6

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?

Rabarberski
  • 23,854
  • 21
  • 74
  • 96
  • i would certainly expect both to print 191. in the second case, first c and a are promoted to int independently and so their multiplication can't overflow. The same happens in the first case (although there, PRR_SCALE is already int - but that won't change promotion of a to int either). your gcc on your box behaves exactly fine. – Johannes Schaub - litb Apr 26 '09 at 22:06
  • check that you have the header stdio.h included. i know that one compiler for msp430 does allow implicit function declarations to happen: if that is the case, the call to printf would cause undefined behavior, and the "0" result would be explained thereby. just my two cents. don't think this is worth an answer :) – Johannes Schaub - litb Apr 26 '09 at 22:09
  • Just out of curiosity: What happens if you set PRR_SCALE to 255U? – Jochen Walter Apr 27 '09 at 08:56
  • @Jochen: the printed result is 255 !? I have no clue about the why (again)... – Rabarberski Apr 27 '09 at 09:22
  • mul code for msp430 uses multiplication emulation code (no hardware support) iirc. so it's quite possible that there is some bug involved i suspect (although this is a fairly C-frontend issue). As a fix, try (+PRR_SCALE * +a) / +b; and add a comment that '+' will promote them manually. Maybe that will help? – Johannes Schaub - litb Apr 27 '09 at 11:24
  • @litb: thanks, but nope. Already tried the unary operator, but that didn't help. I am putting the DEFINEd value in variable as a workaround for now. – Rabarberski Apr 27 '09 at 13:14
  • The compiler appears to be treating the 255 as -1, (-1*3)/4 == 0, which sounds like a compiler error - the handling of the constant integer that is. – Skizz Apr 27 '09 at 13:33
  • I agree with other posters who think your compiler is buggy. According to http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf, Section 6.3.1.1, paragraph 2, in both code examples int-arithmetic should be used. Have you tried sending a bug report to the Contiki team? – Jochen Walter Apr 27 '09 at 20:42
  • I don't know if it is possible but if you can live with a ppr_scale of 256 I would do it with a shift (uint8_t)(((uint16_t)(a))<<8)/b) or (uint8_t)((((uint16_t)(a))<<8)-a)/b) if you really need 255. – Rex Logan May 05 '09 at 14:37

5 Answers5

10

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
  • 32,009
  • 9
  • 68
  • 103
  • "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 Apr 26 '09 at 22:07
  • 1
    "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 Apr 26 '09 at 22:12
  • +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 Apr 26 '09 at 22:18
  • +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 Apr 26 '09 at 22:19
  • 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 Apr 26 '09 at 22:28
  • 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 Apr 26 '09 at 22:32
2

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
  • 1,169
  • 6
  • 8
  • 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 Apr 26 '09 at 21:41
  • 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 Apr 27 '09 at 20:47
2

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.

1

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
  • 86,444
  • 48
  • 176
  • 211
0

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
  • 12,079
  • 22
  • 71
  • 89