views:

530

answers:

5

I have the following C code:

#define PRR_SCALE 255
...
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
printf("prr: %u\n", prr);

If I compile this (using an msp430 platform compiler, for an small embedded OS called contiki) the result is 0 while I expected 191. (uint8_t is typedef'ed as an unsigned char)

If I change it to:

uint8_t a = 3;
uint8_t b = 4;
uint8_t c = 255;
uint8_t prr;
prr = (c * a) / b;
printf("prr: %u\n", prr);

it works out correctly and prints 191.

Compiling a simple version of this 'normally' using gcc on an Ubuntu box prints the correct value in both cases.

I am not exactly sure why this is. I could circumvent it by assigning the DEFINEd value to a variable beforehand, but I'd rather not do that.

Does anybody know why this is? Perhaps with a link to some more information about this?

+2  A: 

255 is being processed as an integer literal and causes the entire expression to be int based rather than unsigned char based. The second case forces the type to be correct. Try changing your #define as follows:

 #define PRR_SCALE ((uint8_t) 255)
Richard
Doesn't work, I had tried that already (well, I did the cast directly in the calculation line. I retried it as you suggested. No difference).
Rabarberski
I both code examples, the arithmetic is done in int. This is due to the integer promotions, see http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf, Section 6.3.1.1, paragraph 2.
Jochen Walter
+1  A: 

I'm not sure why the define doesn't work, but you might be running into rollovers with the uint8_t variables. 255 is the max value for uint8_t (2^8 - 1), so if you multiply that by 3, you're bound to run into some subtle rollover problems.

The compiler might be optimizing your code, and pre-calculating the result of your math expression and shoving the result in prr (since it fits, even though the intermediate value doesn't fit).

Check what happens if you break up your expression like this (this will not behave like what you want):

prr = c * a; // rollover!
prr = prr / b;

You may need to just use a larger datatype.

Andy White
Technically c*a does not overflow. The C standard specifies that an overflow cannot happened for unsigned integer types.
JaredPar
Cool, that makes sense. I guess "rollover" would be a better term.
Andy White
+9  A: 

The short answer: you compiler is buggy. (There is no problem with overflow, as others suggested.)

In both cases, the arithmetic is done in int, which is guaranteed to be at least 16 bits long. In the former snippet it's because 255 is an int, in the latter it's because of integral promotion.

As you noted, gcc handles this correctly.

avakar
"the arithmetic is done in int, which is guaranteed to be at least 16-bit long" -- Can you provide a source on that? I question it's validity...
strager
"the arithmetic is done in int": a result of integral promotion. "[int] is guaranteed to be at least 16-bit long": guaranteed indirectly, INT_MAX must be at least 32767 and INT_MIN at most -32767 (see the section about limits.h in the standard).
avakar
+1, that's what I expected after debugging a bit. Can you provide a link which specifies how the value is converted back? Or is it just modular arithmetic?
JaredPar
+1 what else :) by the way in the case where a binary operator is faced with two operands and the guy wants to bring them to a single type, the conversions done are called "usual arithmetical conversion". among the conversions done by that are integral promotions that you refer to. and that's what happens here in both cases, indeed.
Johannes Schaub - litb
JaredPar, if either the source type or the target type of a conversion is signed, the behavior is only defined if the value can be represented in the target type. For unsigned types, modulo arithmetic is used.
avakar
Of course, on 2's complement machines, a conversion to an unsigned type will most likely follow modulo arithmetic even when the source is signed, so it'll behave as one would expect. :)
avakar
A: 

One difference I can think in case-1 is,

The PRR_SCALE literal value may go into ROM or code area. And there may be some difference in the MUL opecode for say,

case-1: [register], [rom]
case -2: [register], [register]

It may not make sense at all.

Alphaneo
+1  A: 

If the compiler in question is the mspgcc, it should put out an assembler listing of the compiled program together with the binary/hex file. Other compilers may require additional compiler flags to do so. Or maybe even a separate disassembler run on the binary.

This is the place where to look for an explanation. Due to compiler optimizations, the actual code presented to the processor might have not much similarity to the original C code (but normally does the same job).

Stepping through the few assembler instructions representing the faulty code should reveal the cause of the problem.

My guess is that the compiler somehow optimizes the whole calculation sice the defined constant is a known part at compile time. 255*x could be optimized to x<<8-x (which is faster and smaller) Maybe something is going wrong with the optimized assembler code.

I took the time to compile both versions on my system. With active optimization, the mspgcc produces the following code:

#define PRR_SCALE 255
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
    40ce:   3c 40 fd ff  mov #-3, r12 ;#0xfffd
    40d2:   2a 42        mov #4, r10 ;r2 As==10
    40d4:   b0 12 fa 6f  call __divmodhi4 ;#0x6ffa
    40d8:   0f 4c        mov r12, r15 ;
printf("prr: %u\n", prr);
    40da:   7f f3        and.b #-1, r15 ;r3 As==11
    40dc:   0f 12        push r15  ;
    40de:   30 12 c0 40  push #16576  ;#0x40c0
    40e2:   b0 12 9c 67  call printf  ;#0x679c
    40e6:   21 52        add #4, r1 ;r2 As==10

As we can see, the compiler directly calculates the result of 255*3 to -3 (0xfffd). And here is the problem. Somehow the 255 gets interpreted as -1 signed 8-bit instead of 255 unsigned 16 bit. Or it is parsed to 8 bit first and then sign-extended to 16 bit. or whatever.

A discussion on this topic has been started at the mspgcc mailing list already.