5
int main() 
{
  unsigned int i = 12;
  printf("%lu", i); // This yields a compiler warning
}

On a 32 bit platform, does using printf with an int using %lu result in garbage?

Steve Dunn
  • 21,044
  • 11
  • 62
  • 87
bryantism
  • 167
  • 9

3 Answers3

8

Only the statement "32 bit platform" doesn't mean that int and long both have 32 bits, as well as their unsigned counterparts.

So yes, indeed this can happen if unsingned long, what %lu is made for, is longer than unsigned int.

But even if the lengths are equal, the types are not compatible, so formally it is undefined behaviour.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • how can i simulate this kind of situation? – bryantism Apr 25 '14 at 07:54
  • You have right (-> +1 ) but it is so for nearly all of the current compilers. – peterh Apr 25 '14 at 07:55
  • In `LP64` (http://en.cppreference.com/w/cpp/language/types), `long` is 64 bits and `int` is 32 bits. – nneonneo Apr 25 '14 at 08:06
  • LP64 is not a 32 bit platform. I've never seen a 32 bit platform where long and int were not both 32 bits, and most of my work has been on 32 bit platforms: Sparc, HP/PA, Power PC, even some Intel in the last couple of years. And all, in 32 bit mode, had both `int` and `long` 32 bits. – James Kanze Apr 25 '14 at 08:11
  • 1
    This is the wrong explanation. The standard requires `%lu` to be matched to an `unsigned long` type. The types `unsigned long` and `unsigned int` are not the same even if they have the same width, and it is undefined behavior to pass one for the other even if they do. – Pascal Cuoq Apr 25 '14 at 08:31
  • C11 7.21.6.1:9 “[…] If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.” open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf – Pascal Cuoq Apr 25 '14 at 08:35
  • @PascalCuoq Ok, I added a statement clarifying this. – glglgl Apr 25 '14 at 08:51
4

If the required type and the given type are not compatible, you have undefined behavior. It's entirely legal for a compiler to pass type information with the value when passing a vararg, and exploit it in va_arg (although I don't know of any which do, probably for historical reasons).

As for practical effects in your particular case, "%lu" expects an unsigned long. The only other type which is compatible is long, and then only if the actual value of the long is non-negative. Passing it an int is undefined behavior, although it might work. (On most 32 bit platforms, int and long have the same size and representation.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • if int is 4 bytes, long 8 bytes, this really can result in undefined behavior? – bryantism Apr 25 '14 at 08:05
  • 1
    @bryantism The result really is undefined already. "Appearing to work" is a valid undefined behaviour. But yes, on a platform where `int` and `long` have different representations you would be more likely to see something obviously odd. – BoBTFish Apr 25 '14 at 08:11
  • @bryantism Even if both `int` and `long` are 4 bytes, the behavior is undefined. As a said, an implementation could pass type information along with the value, and use it in `va_arg` to ensure that the correct type was passed. – James Kanze Apr 25 '14 at 08:26
  • @BoBTFish If the platform is little endian, and there are no other varargs, it's still likely to appear to work (but of course, it's still totally undefined behavior). – James Kanze Apr 25 '14 at 08:27
1

honestly I do not understand why you should use the %lu in place of %u since you are working with an int.

%lu should be used (in its very basic explanation) for unsigned long.

It will most probably print garbage if your compiler uses (and of course it does in 99% of the situation) different storage size for int and long.

For instance, according to C standard, an unsigned int is, in terms of storage size "At least 16 bits in size. " while unsigned long is "At least 32 bits in size."

Now, let's take as an example 16 bits for int and 32 bits for long and lets' consider a untypical example where the memory is all zeroed at the moment you run the program.

You value 12 is represented in memory as:

00000000 00001100

and if you would print with an %u would result in 12:

In place, if instruct printf to print as %lu it would result that the memory taken in the printf is:

00000000 00001100 00000000 00000000

which corresponds to the long value of 786432

Edit: Passing the value of the variable to the printf function (and not the pointer (and the size) of the variable) makes the code working anyway. My previous "explanation" was mostly to explain why the warning is raised and why it is "typically" a wrong approach.

Maurizio Benedetti
  • 3,557
  • 19
  • 26
  • 4
    I'd disagree that in 99% of the situations, long and int have different sizes. On all of the 32 bit platforms I've seen, they have the same size. – James Kanze Apr 25 '14 at 08:06
  • 1
    Also, of course, the issues regarding `printf` are more complex, but on the most common platforms (Intel based), integers are little endian, so bytewise, his 12 would be 0C 00 (in 16 bits) or 0C 00 00 00 (in 32 bits). – James Kanze Apr 25 '14 at 08:08
  • thanks, I did not care this kind of problem before, until I meet the problem today, It really printf a garbage value in the the production code debug log, maybe our gcc is tooooooooooo old... – bryantism Apr 25 '14 at 08:15
  • James, for instance on my I7, 64 bit Linux distro, the size of int is 4, the size of long is 8. To be honest with you, I confirm what I have previously stated, most of the time they are different. Trying to make a simple sizeof(int) and sizeof(long) would report the difference. – Maurizio Benedetti Apr 25 '14 at 08:18
  • @MaurizioBenedetti yes, 64bit linux usually got different sizes for int and long but on windows they are the same even under 64 bit. Also OP specifically talks about 32 bit platforms, where they are the same virtually everywhere. So I wouldn't say they are 'different 99% of the time. – AliciaBytes Apr 25 '14 at 08:34