C warnings, %d versus %u and more C fun.

Code cleanup on aisle five…

I recently spent two days at work going through projects to clean up compiler warnings. In GNU C, you can enable options such as “-Wall” (all warnings), “-Wextra” (extra warnings) and “-Werror” (warnings as errors). By doing steps like these, the compiler will scream at you and fail to build code that has warnings in it.

Many of these warnings don’t impact how your code runs. They just ask you “are you sure this is what you are meaning to do?”

For example, if you leave out a “break” in a switch/case block, the compiler can warn you about that:


x = 1;

switch( x )
{
case 1:
printf("x is onen");
// did I mean to not have a break here?

case 2:
printf("x is twon");
break;

default:
printf("I don't know what X isn");
break;
}




This code would print:

x is one
x is two

...because without the "break" in the "case 1", the code drops down to the following case. I found several places in our embedded TCP/IP stack where this was being done intentionally, and the author had left comments like "/* falls through below */" to let future observers know their intent. But, with warnings cranked up, it would no longer build for me, even though it was perfectly fine code working as designed.

I found there was a GCC thing you could do where you put in "//no break" as a comment and it removes that warning. I expect that are many more "yes, I really mean to do this" comments GCC supports, but I have not looked in to it yet.

Size (of int) matters

Another issue I would see would be warnings when you used the wrong specifier in a printf. Code might compile fine without warning on a PC, but generate all kinds of warnings on a different architecture where an "int" might be a different size. For example:

int answer = 42;
printf("The answer is %dn", answer);

On my PC, "%d" can print an "int" type just fine. But, if I had used a "long" data type, it would error out:

long answer = 42;
printf("The answer is %dn", answer);

This produces this warning/error:

error: format '%d' expects argument of type 'int', but argument 2 has type 'long int' [-Werror=format=]|

You need to use the "l" (long) specifier ("%ld") to be correct:

long answer = 42;
printf("The answer is %ldn", answer);

I found that code that compiled without warnings on the PC would not do the same on one of my embedded target devices.

%u versus %d: Fight!

Another warning I had to deal with was printf() and using "%d" versus "%u". Most code I see always uses %d, which is for a signed value which can be positive or negative. It seems works just fine is you print an unsigned integer type:

unsigned int z;

z = 100;
printf("z is %dn", z);

Even though the data type for z is unsigned, the value is positive so it prints out a positive number. After all, a signed value can be positive.

But, it is more correct to use "%u" when printing unsigned values. And, here is an example of why it is important to use the proper specifier... Consider this:

#include <limits.h> // for UINT_MAX

unsigned int x;

x = UINT_MAX; // largest unsigned int

printf("x using %%d is %dn", x);
printf("x using %%u is %un", x);

This prints:

x using %d is -1
x using %u is 4294967295

In this case, %d is not giving you what you expect. For a 32-bit int (in this example), ULONG_MAX of 4294967295 is all bits set:

11111111 11111111 11111111 11111111

That represents a -1 if the value was a signed integer, and that's what %d is told it is. Thus, while %d works fine for smaller values, any value large enough to set that end bit (that represents a negative value for a signed int) will produce incorrect results.

So, yeah, it will work if you know you are never printing values that large, but %u would still be the proper one to use when printing unsigned integers... And you won't get that warning :)

6 thoughts on “C warnings, %d versus %u and more C fun.

  1. James Jones

    It would be nice if compilers and lint programs agreed on comments that mark “No, I really meant to do this” situations. Some flavor of lint I used wanted /* FALLTHROUGH */ to mark intentional case fallthroughs.

    OTOH, I think Go has the right idea. You don’t have a special statement to break; instead, you have a fallthrough statement required to fall through. As a consequence:

    1. The more common case, not falling through, is the short one.
    2. The less common case is explicitly pointed out–makes control flow bugs less likely.
    3. It avoids the major C irritant of using the same [expletive] keyword for getting out of a switch statement and getting out of a loop. In C, if you loop around a switch statement and in some cases want to get out of the loop, all your alternatives are all ugly.

    Reply
    1. Allen Huffman Post author

      The Go implementation makes sense. I need to find a free Lint for us to start using at work. I have our code compiling without errors now (all, extra) but I had to ignore the tcp/ip stack we use since it’s …. not. Lint could find more issues that the compiler won’t.

      Reply

Leave a Reply to James JonesCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.