4

I have the following warning generated by a printf():

warning: format '%llu' expects argument of type 'long long unsigned int', but argument 7 has type 'uint64_t' {aka 'long unsigned int'} [-Wformat=]

Although I want to create a warning-free code, I think that the compiler is way too over-zealous. Because:

  • long unsigned is unsigned and has 64 bits (it is there, in the text of the message)
  • long long unsigned is also unsigned, and cannot be shorter than 64 bits (C standard, in comparison to unsigned long), and also cannot have more than 64 bits (hardware limitation, not enough bits in the processor registers).

So for any practical purpose, long unsigned and long long unsigned are actually the same thing, right?

Even more, while long unsigned can have 32bits on some platforms (e.g., Widows), long long unsigned is highly unlikely to have another number of bits (than 64). long long was created especially to support 64 bits.

So what is going on? How should I proceed? What should I understand?


Side question:

Let's assume that the following is true:

typedef long unsigned int uint64_t;

And let's assume that on some platform, long unsigned becomes 32 bits, and in that case, long long unsigned is not anymore the same as long unsigned. But then uint64_t is not 64 bits anymore - which means that the definition of uint64_t will have to change to remain 64 bits. So in the end, long long unsigned and uint64_t will still be the same size and sign-ness.

So is my thinking correct, that the compiler should not have given me that message at all? Because it is not applicable, no matter how things are, or will be?


I currently use Codeblocks with Cygwin C compiler under Windows - in order to have long unsigned of 64 bits, instead of 32.

The command line for compiling (copy-paste):

gcc.exe -Wshadow -Winit-self -Wredundant-decls -Wcast-align -Wundef -Wfloat-equal -Winline -Wunreachable-code -Wmissing-declarations -Wswitch-enum -Wswitch-default -Wmain -pedantic -Wextra -Wall -m64 -O0 -c Z:/_progs/_progsData/cb/myprog.c -o obj/Debug/myprog.o

5
  • 1
    If the maximum integer size was limited by the size of a processor register, C would not have got very far. Commented Jul 15 at 7:24
  • 3
    You haven't shown your line of code, if it uses say uint64_t num; then try the correct format specifier printf("num = %" PRIu64, num); Commented Jul 15 at 7:31
  • @WeatherVane: Your assumption is correct, I use the types as stated in the warning message. PRIu64 would be more suitable, albeit uglier :) Commented Jul 15 at 8:41
  • 1
    By your reasoning ("amounts to the same as") you could justify all kinds of undefined behaviour. If your unsigned long and unsigned long long both have 64 bits, then the correct specifier for unsigned long is %lu not %llu. Commented Jul 15 at 9:08
  • 1
    They are not the same type, for the stdint.h types you should use the PRIxXX macros. In your case uint64_t n = 2; printf("%"PRIu64"\n", n); Commented Jul 15 at 9:11

2 Answers 2

2

The warning underscores a portability issue. Using %llu for a uint64_t would have undefined behavior on architectures where uint64_t is an unsigned long and unsigned long long would have 128 bits, which would make sense if 128-bit arithmetics are supported.

This would not happen on legacy systems where long is still 32 bits, but the C Standard does support this possibility.

You can fix the code in different ways:

  • use the PRIu64 macro defined in <inttypes.h> (quite ugly and convoluted, but this is the correct solution):

    int print_uint64(uint64_t val) {
        return printf("%" PRIu64, val);  // a bit cryptic but correct
    }
    
  • cast the values as (unsigned long long) (ugly and verbose too):

    int print_uint64(uint64_t val) {
        return printf("%llu", (unsigned long long)val);
    }
    
  • use %lu... but this would make your code non portable to legacy architectures where long still has just 32 bits.

  • use your own type for 64-bit ints typedef unsigned long long u64; and you can use %llu on all platforms where long long has 64 bits, and worry about portability to 128 bit systems later:

    #include <limits.h>
    #include <stdint.h>
    #include <stdio.h>
    
    #if ULLONG_MAX == UINT64_MAX
    typedef unsigned long long u64;
    typedef          long long i64;
    #else
    #error "this platform does not have 64-bit long long types"
    #endif
    
    int print_u64(u64 val) {
        return printf("%llu", val);
    }
    
Sign up to request clarification or add additional context in comments.

8 Comments

I am afraid about the times when the C standard will be upgraded to support the long long long int type, for 128 bits :) I was also afraid that I would receive this kind of answer, but I had some hopes that I was missing something.
I accept this answer because it answers both the question and the intention, without stepping to the sides. It also provides some suitable solutions. I willingly give away the right to wait for a better answer, but I keep the right to change the decision later :)
the only correct solution is to use the correct format specifiers PRIxXX
Before reading this answer, I did not really take into consideration the forward compatibility (with 128 bit systems).
@chqrlie Could be that they don't want inconsistency but int128_t / uint128_t are allowed to exceed the normal boundaries of intmax_t and uintmax_t (which would be odd but allowed).
@Fredrik: I updated my answer to emphasize the correct answer :)
|
2
  • long unsigned is unsigned and has 64 bits (it is there, in the text of the message)

It may have 64 bits on your system but is not specified to have that.

So for any practical purpose, long unsigned and long long unsigned are actually the same thing, right?

No, they are distinct types. If they were the same, the below would compile, but it doesn't, no matter if the word length of unsigned long is the same as that of unsigned long long:

void foo(unsigned long long) {}

int main() {
    unsigned long ul = 123;
    _Generic(ul, unsigned long long: foo)(ul);
}

So you are using an invalid conversion specification for the type of variable you've provided.

From C23 (first post publication, n3220):

7.23.6.1 The fprintf function
  1. If a conversion specification is invalid, the behavior is undefined. fprintf shall behave as if it uses va_arg with a type argument naming the type resulting from applying the default argument promotions to the type corresponding to the conversion specification and then converting the result of the va_arg expansion to the type corresponding to the conversion specification. Note 329: )The behavior is undefined when the types differ as specified for va_arg.
3.5.3.1 undefined behavior

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements

How should I proceed?

Make sure you always use the correct conversion specification.

For uint64_t, the correct conversion specification is available through the macro PRIu64 from <inttypes.h>.

uint64_t var = 123;
printf("The value is %" PRIu64 " and here the string continues\n", var);

What should I understand?

You need to understand that even if unsigned long and unsigned long long have exactly the same number of bits, they are not the same thing and since using an invalid conversion specification makes the program have undefined behavior, the compiler is doing a good thing by emitting a warning,

11 Comments

Even though your answer is factually correct, it is also not an answer to my question. Sorry.
Ditto with MSVC's types int and long. They are the same size but distinct types.
Each formatting instruction in a printf format string, such as %lu, is, in the terminology of the C standard, a conversion specification. It tells printf how to convert the “raw bytes” of the valued passed to it to decimal numerals, characters, or other output forms. This is formatting, but it is also conversion. A conversion is generally any function whose output is the same value (or close) as the input, just in a different form. Formatting a binary representation as a decimal numeral is a conversion (binary “0011” and English “three” and decimal “3” all represent the same value)…
… The particular character that specifies the type of the conversion (u in %lu) is a conversion specifier. Other parts of the specification may be a modifier (the l), a precision, a field width, or a flag. So you did use the correct conversion specifier (u is correct), but your conversion specification is wrong (%llu is wrong).
@EricPostpischil Thanks! Seems I've used specifier and specification interchangeably. Will try to keep that distinction in mind.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.