0

Using arm-none-eabi-gcc, the following program gives a different output depending on compiler optimisation level:

#include <stdio.h>

unsigned short p = 100;

void f(signed char o) {
    // o is signed, so 0xfc should be interpreted as -4 here, but it is not always
    p += o;
}

int main(void) {
    void (*fp)(unsigned char o);
    fp = (void (*)(unsigned char))f;

    printf("%d\n", p);

    fp(0xfc);

    printf("%d\n", p);

    return 0;
}

Output with -O0 (desired): 100 96

Output with -O2 (undesired): 100 352

I wish for the program to output 96, when using -O2.

Using -fwrapv, or -fno-strict-overflow has no affect.

I cannot change the type of fp, I want f to always interpret the first parameter as a signed char (so it sees 0xfc as -4).

user2868331
  • 333
  • 4
  • 10
  • possible duplicate of [How do promotion rules work when the signedness on either side of a binary operator differ?](http://stackoverflow.com/questions/6770258/how-do-promotion-rules-work-when-the-signedness-on-either-side-of-a-binary-opera) – geometrian Mar 11 '15 at 00:05
  • 1
    You need to provide a [Minimal Complete Verifiable Example](http://stackoverflow.com/help/mcve). – user3386109 Mar 11 '15 at 00:05
  • you should post actual, compiling code, even the two lines posted have an error as the second line is referencing 'o' and not 'p' – user3629249 Mar 11 '15 at 00:19
  • that's not an error, that's the output. – user2868331 Mar 11 '15 at 00:21
  • @imallett that's a C++ thread, and doesn't seem to explain OP's issue anyway – M.M Mar 11 '15 at 00:49
  • The `printf` is erroneous and irrelevant. Post the complete code of the function with the actual value of `p` and the observed behaviour. What do you get with `-O0` and what with `-O2`. – chqrlie Mar 11 '15 at 07:01
  • I have posted a more complete sample. – user2868331 Mar 11 '15 at 16:28

2 Answers2

0

Calling a function through a pointer of different type is undefined behavior.

There are no guarantees. The compiler is doing the "right" thing, because anything is allowed.

You said you cannot change the type of fp, therefore the fix is to change the type of f:

void f(unsigned char uo)
{
    signed char o = (signed char)uo; // value is implementation-defined :(
    p += o;
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
-2

The problem here is unsigned promotion. Due to a somewhat surprising behavior in C, binary operators on an unsigned value and a signed value will promote the signed value to be unsigned before the operation.

Yes. It's weird. Try this program to confirm (live demo):

#include <stdio.h>
int main(void) { printf("%d",(int)( -1 < 0u )); return 0; }

-1 is surely less than 0, and yet this program outputs false. What happened is that -1 got promoted to an unsigned value before the comparison (since, unlike 0, 0u is unsigned). Since no unsigned number can be less than unsigned zero, the comparison then evaluates to false.

I very strongly suspect this is what's happening in your example:

signed char o = -4;
unsigned short p = /*...*/;
p += o;

Because p is unsigned, o gets converted to an unsigned char: the bit pattern of -4 in two's complement is 0xFC, which is 252 if interpreted as unsigned.

Since you now have two unsigned operands, the addition can now proceed, and 252 is added onto p.


As far as what to do about it? Well, you could make p a signed variable so that this doesn't happen. You could subtract a positive value instead of adding a negative one. It's hard to know without seeing your use case.

geometrian
  • 14,775
  • 10
  • 56
  • 132
  • this answer is wrong, unfortunately. `o` never gets converted to an `unsigned char`. The integral promotions are the first thing that happen. On a system where `sizeof(short) < sizeof(int)`, then both `o` and `p` are promoted to signed `int`. On other systems the calculation is done as `unsigned short` (or `unsigned int` in the rare case of sizeof(int)==1), however the result will still appear as I described in my answer; e.g. `(unsigned short)7 + (unsigned short)-4` gives `(unsigned short)3`. – M.M Mar 11 '15 at 05:24
  • Possibly he is using plain `char` and assumes it is signed when in fact it is unsigned, although it's really impossible to say until he posts his code. – M.M Mar 11 '15 at 05:25
  • slight correction, the second case after usual arithmetic conversion is `(unsigned int)p + (unsigned int)(int)o`, but same result – M.M Mar 11 '15 at 05:36