0

Compiling this MCVE

#include <stdio.h>
#include <climits>
#include <limits>

int main()
{
    unsigned long x = ULONG_MAX;
    printf( "1:=%lu - 2:=%lu 3:=%ld\n", std::numeric_limits<size_t>::max(), x, x );
}

with these gcc/clang [any version] options -Wall -Werror -pedantic does not fail.

This is the output:

1:=18446744073709551615 - 2:=18446744073709551615 3:=-1

I expected an error because I provide an unsigned int but the format specifier is signed int.

Compiling it with PowerPC gcc 4.8 fails as expected:

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

My question is:

Why do gcc/clang compile these format specifier? For me I would say PowerPC gcc is correct because this is a serious sign/unsigned issue and the wrong format specifier displays an incorrect result.

Peter VARGA
  • 4,780
  • 3
  • 39
  • 75
  • 3
    The standard doesn't require compilers to check format specifiers. –  Feb 18 '17 at 12:13
  • @NeilButterworth This is not the question about standard - this is a question how the compilers behave, even in their [documentation](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) is something else stated: – Peter VARGA Feb 18 '17 at 12:14
  • 1
    The exact type of `size_t` is not specified, except that it has to be unsigned. Many platforms have it a `unsigned long`. Others, like the PPC platform you test on apparently have it as `unsigned int`. For `size_t` you should always use `"%zu"` (or `"%zx"` for hexadecimal, or `"%zo"` for octal). – Some programmer dude Feb 18 '17 at 12:16
  • I don't follow. You want to print an `unsigned long` (with the specifier `"%lu"`), and wonder why you don't get an error when the underlying type of `size_t` is `unsigned long`? Remember that `size_t` is not a native type, it's a type-alias, created with `typedef`. – Some programmer dude Feb 18 '17 at 12:19
  • @Someprogrammerdude I don't follow. Please check the MCVE again carefully. Where is the format specifier wrong Mister? Not in the `size_t` parameter, it is a native `unsigned long` variable. – Peter VARGA Feb 18 '17 at 12:21
  • 1
    By the way, the code you show in the question, and the code that gives you an error is not the same! And the error complains about *argument 2* which is not the `"%ld"` format specifier you perhaps wonder about? Can you perhaps make an example with *only* the format specifier and argument/value that you wonder about? – Some programmer dude Feb 18 '17 at 12:26

2 Answers2

5

As per GCC (min version 5.0) documentation,

-Wformat-signedness If -Wformat is specified, also warn if the format string requires an unsigned argument and the argument is signed and vice versa.

This setting is not default because the standard doesn't ask for it. If you want more than the standard, you need to use the correct flags. In your case, you are using -Wformat via -Wall but not -Wformat-signedness. Remember, -Wall does not switch on all warnings, just a sub-set which almost all people agree on.

Compilers do check for type safety, hence why you get a warning when you remove the long-specifier. Beyond that, if the data is not getting lost (as in sign to unsigned or unsigned to sign or promotion from int to long, etc.) they feel free to do nothing. In fact, clang doesn't have a flag for sign checking the formats (AFAIK).

Kunal Tyagi
  • 549
  • 4
  • 12
1

Using printf in an inappropriate way is an instance of undefined behavior. The implementation can do very bad things (including apparently what you would dream it to do).

BTW, with C++, you'll better use std::cout and its operator << which is less error-prone.

Why do gcc/clang compile these format specifiers?

Most of the time, it does not (however, the compiler may know about printf and sometimes optimize calls to it, because your <stdio.h> header would use specific format function attributes). In your case, the compiler is probably emitting a call to the variadic printf provided by your standard C library. It is not compiling format specifiers (just passing literal constants as first argument to printf).

BTW, the control format string for size_t in printf is %zu

At last, language specifications do not define what the compiler should warn about.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • That is your opinion, and actually it depends a lot. – Basile Starynkevitch Feb 18 '17 at 12:23
  • 1
    @Al They are however typesafe, unlike printf and scanf, as you have discovered. –  Feb 18 '17 at 12:24
  • GCC *does* look at `printf` arguments and try to issue warnings if they are wrong, if the appropriate setting is used. See also the `format` attribute in the page you linked to. – interjay Feb 18 '17 at 12:29
  • @AlBundy: Type unsafety is a much greater nightmare than C++ streams could ever be. But you can get the best of both worlds (type safety + flexibility of format strings) anyway, using Boost.Format. – Christian Hackl Feb 18 '17 at 12:34
  • 1
    @AlBundy: The only real reason to use format strings is when the format string is **dynamic** and not static. The only disadvantage of C++ streams is that they are always a static construct. And a compiler obviously cannot detect wrong format strings when they are not known at compile time. So the problem is far from "solved". I must say that you have some very particular impressions about what things are nightmarish in C++ and which aren't. – Christian Hackl Feb 18 '17 at 12:38