Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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)

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.

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top