6

In some embedded C code for the MC9S12C32 microcontroller, I have a circular queue (aka circular buffer) implemented with a statically sized byte array and two "pointers" for the front and rear of the queue, which are in reality just indices for the queue's array.

// call unsigned chars bytes
typedef unsigned char byte;
byte trear  = 0;   // SCI transmit display buffer IN index
byte tfront = 0;   // SCI transmit display buffer OUT index
byte tsize  = 16;  // size of transmit buffer
byte tbuf[16]= {0};// SCI transmit display buffer

Note that trear is the actual index of the rear element, but tfront is one less than actual index of the front element (subject to modulo 16 of course). So, for example, if my buffer contained "hello", it might look like this (where empty slots are garbage values):

_________________________________
| | |h|e|l|l|o| | | | | | | | | |
   ^         ^
 front      rear

When it's time to remove a byte from the queue, I do this:

// increment front index
tfront++;
// wrap front index if it exceeded bounds
tfront %= tsize;                           // (A)
// get character to transmit
byte outputChar = tbuf[tfront];

This all works fine -- at least, my program has exhibited no bugs related to this fragment. However, when I compile this program, my compiler warns me about the line marked (A) in the fragment above, complaining:

Warning : C2705: Possible loss of data

main.c line 402

Line 402 is line (A). I should note that I am not using gcc or the like; I am compiling in Freescale's CodeWarrior IDE, which has sometimes given me other somewhat mystifying warnings. In an attempt to get rid of the warning, I rewrote the fragment above as:

// increment front index mod tsize
tfront = (tfront + 1 >= tsize) ? 0 : tfront + 1;      // (B)
// get character to transmit
byte outputChar = tbuf[tfront];

However, my compiler still emits the same warning, this time about line (B). Maybe the compiler is telling me that in the statement (tfront + 1 >= tsize), tfront might be 255 before execution, and overflow. Of course, I know this won't happen, but my compiler doesn't.

If this is the case, though, why was line (A) an issue? Basically, I'd like to know what the compiler is unhappy about.


Since typing out my question, I've solved it by changing tsize from a variable type to a preprocessor definition (i.e., #define TSIZE 16). My question still stands, though.


Some related questions:
unsigned overflow with modulus operator in C
modulus operator with unsigned chars

Community
  • 1
  • 1
ravron
  • 11,014
  • 2
  • 39
  • 66
  • 1
    Note that if `tsize` can be guaranteed to be a power of 2, it is more efficient to use a bit mask, and that also neatly avoids this warning without a cast. So for a buffer of 16 elements, `tfront |= 0x0f` rather than `tfront %= 16`. In the general case for any `tsize` that is a power of 2, the required mod-mask is `tsize - 1`. – Clifford Nov 14 '13 at 15:53
  • @Clifford If the warning is caused by the assignment expanding to `lval = expr;` where `lval` has type `byte` and `expr` has type `int`, then using a bit mask does not avoid the warning: `tfront &= mask;` expands to `tfront = tfront & mask;` and `tfront & mask` has type `int` for the same reason `tfront % size` does (C99 6.3.1.1:2 and 6.3.1.8:1 “the integer promotions are performed on both operands”). – Pascal Cuoq Nov 14 '13 at 17:19
  • @Clifford In addition, one needs to get the bitwise operator right. Did you mean `&`? – Pascal Cuoq Nov 14 '13 at 17:21
  • @PascalCuoq: Doh! `tfront &= 0x0f`, and agreed on the warning - wrong thinking again. Let's just go with the efficiency improvement then. It is particular significant on architectures without a DIV instruction, though HCS12 is not one of them. – Clifford Nov 14 '13 at 17:41

2 Answers2

5

The compiler warning likely comes from the fact that in tfront %= tsize;, which is equivalent to tfront = tfront % tsize;, because of promotion rules in C the expression tfront % tsize has(*) type int.

It may silence the compiler if you write tfront = (byte)(tfront % tsize); instead.

There is no particular reason to worry, and your compiler emits strange warnings indeed: although the expression tfront % tsize technically has type int, its values all fit in a byte because of the way it is computed. Even if the values did not all fit in a byte, the wrap-around behavior is guaranteed by the C standard for unsigned integer types (so that you would be justified in using this wrap-around behavior on purpose).

(*) unless on your compilation platform int cannot contain all values that an unsigned char can take, in which case it would be of type unsigned int and you probably wouldn't see the warning.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
  • Just as you suggested, this appears to be the issue. Very insightful, thanks. – ravron Nov 14 '13 at 15:20
  • 2
    The warning is singularly unhelpful; false positives are worse than false negatives because they undermine the mind set 'compilation without warnings is good'. Report a bug to the compiler supplier. – Jonathan Leffler Nov 14 '13 at 15:23
  • @JonathanLeffler I have already reported this very bug myself, some years ago. Not sure if it is still there. See my answer for details. – Lundin Nov 15 '13 at 08:04
3

This particular warning is a known bug in all of the Codewarrior compilers. The possible loss of data warning is inconsistent and buggy. Sometimes it warns because there is a risk of implicit type promotions, sometimes it does not. See this discussion. I can confirm that this is also true for CW for S12C (at least as far as version 5.1, which is what I am using).

You can disable this warning, but I wouldn't recommend it, since it does find dangerous code sometimes, along with the false warnings. As in your specific case: the warning is correct.

You are doing arithmetic on small integer types, without explicit casts. Such code is dangerous and possibly contains hidden bugs, because C promotes small integers to signed ints. A coding standard like MISRA-C, which enforces explicit casts, would have helped here.

Furthermore, the ?: operator can also be dangerous unless you know how it works. It balances the 2nd and 3rd operand against each other, which one perhaps did not expect.

I would therefore suggest changing the code to:

tfront = (byte)(tfront % tsize); 
...

if( (byte)(tfront +1) >= tsize)
{
  tfront = 0;
}
else
{
  tfront++;
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I am not convinced by your `(byte)(tfront +1) >= tsize` suggestion. The only opportunity it has to be different from the original condition in the question is if `(byte)(tfront +1)` is different from `tfront +1`, and when it is, the result of your suggested condition is probably not what the programmer intended. Are you sure you are not letting compiler warning considerations force you to put in a bug where there wouldn't normally have been one? If you want to match types, `(unsigned int)tfront +1 >= (unsigned int)tsize` is probably better. – Pascal Cuoq Nov 15 '13 at 11:52
  • @PascalCuoq Yeah the cast on that particular row is somewhat picky, it would only cause trouble in some rare case such as `tfront=255`. Wrap-around to zero is well-defined for unsigned variables, so if the programmer wrote the code relying on that behavior, it wouldn't work as expected because `tfront` gets implicitly promoted. The problem with bugs and oddities related to implicit promotion is always just that: what did the programmer actually intend? Did they expect the oddity to happen or does it come to them as a surprise? It is very important to put comments in the code in such cases. – Lundin Nov 15 '13 at 12:10