Learning something about printf, of all things…

I’ve been C programming for… (quick arithmetic) roughly 25 years now, and yet, there are still things to learn. For instance, I decided to move my code for Milhouse back from my AMD64 Linux box to my Macbook for a little “mobile hacking” over the next week. I quickly found that unlike gcc on the Linux box, gcc on this Mac still thought that “long” variables were 32 bit. Various counters in Milhouse are 64 bit values, as are the hash values that are used in the transposition table, and I quickly found out that all the places where I previously used “%ld” as a format string had to be changed to “%lld”. Grumble!

You see, here’s the annoying thing about C: you know that shorts can hold char values, and ints can hold shorts, and longs can hold ints, but you actually don’t know how many bits any of these have without peeking using sizeof. Luckily, the C standard requires an include file sys/types.h which has typedefs which include types of various sizes, so if you really want a 32 bit unsigned int, you can use the type uint32_t and be reasonably sure that it will work. Such was the state of my knowledge a couple of days ago.

But here’s the thing: I didn’t know any way to generate the right format string for a particular size of data value. On my AMD box, %ld is used for 64 bit values. On my mac, I need to use %lld. Grumble!

But apparently this was all thought of by the C99 standards committee. They created an include file called inttypes.h which includes defines which tell you what format is needed. For example: PRIu64 is the code for a 64 bit unsigned integer value. On my mac, it expands to "ll" "u", which the C preprocessor is nice enough to cat together. Therefore, to print such a value, you need a line like:

printf("%" PRIu64 "\n", sixtyfourbitvalue) ;

Sure, it’s ugly. You think they would at least include the % in the macro. But, it does work. I’m tidying up all my code to play by these nice rules.

14 thoughts on “Learning something about printf, of all things…

  1. Tom Duff

    They don’t include the % so that things like “%16” PRIu64 “\n” will work.

  2. Phil Howard

    The solution to this that I have been using for 64 bit values for a few years is to always use %lld or %llu for the format, and always cast the argument to (long long) or (unsigned long long).

  3. Minimiscience

    Obligatory pedantry: is specified by POSIX, not C99. int32_t and its relatives are defined in the standard C header file . Moreover, exact-width integer types for 8, 16, 32, and 64 bits are optional and are only present if the implementation actually has types with those widths; to be truly portable, you have to use int_least32_t and friends (also defined in ). See section 7.18 of the C99 standard for details.

  4. Minimiscience

    The above comment was supposed to say that the header sys/types.h is specified by POSIX, while the types mentioned are defined in stdint.h. However, I put angle brackets around the header names, and apparently your blog software doesn’t escape special HTML characters. I now have an urge to try to break your site, but I’ll settle for blinking text instead.

  5. mark

    More reasons to hate C.

    The thing I hate most in C is actually the absolute path for header files.

    This “feature” impacted EVERYTHING in the *nix world. It dictated the layout of the structure, and it STOPPED any alternative ideas (because anyone who would want to change that, would have to use another language, and we all know that this isn’t going to happen because C is actually USEABLE.)

    After 25 years it shows you that a language can still surprise you?

    Sounds as if you are too dumb, or the language sucks. :One of these must be true, and I think you are not dumb … >

  6. Art

    I had to deal with this issue a few years back while working on some C code that had to print out a few 64bit values, and I distinctly remember coming across those #defines, and thinking that it was nifty. Thanks for reminding me about it!

  7. Nathan

    @Mark, regarding absolute paths for header files, you can usually set an include path that will tell the compiler where to look for the header files, so you don’t need an absolute path.

  8. Oren Ben-Kiki

    By leaving out the ‘%’ they allow you to write stuff like printf(“%04” PRIu64 “\n”, …) which would be otherwise impossible.

  9. Jared

    The string concatenation you referenced doesn’t come from the C preprocessor. Instead it comes from the “string concatenation” phase of translation. See:
    http://msdn.microsoft.com/en-us/library/bxss3ska%28VS.80%29.aspx

    String concatenation comes out to be a pretty novel idea syntactically. Because a literal string’s type is a const char * (also written char const *), placing two strings next to each other has the effect of removing the null character at the end of the first literal string. Then the addresses of the first character up until 1 – the null character can represent the concatenated string.

  10. Keith Thompson

    A quibble: For historic reasons, C string literals are not of type const char*.

    In fact, a C string literal is of type char[N], where N is the length of the literal plus 1 (to allow for the terminating ”). For example, sizeof(“hello, world”) yields 13, not the size of a pointer. In most contexts, an expression of array type is implicitly converted to a pointer to the array’s first element, so string literals usually, but not always, “decay” to char*.

    As for the “const”, it would make more sense for this to be applied to string literals, but it would have broken old code (pre-ANSI C didn’t have “const”, and code like func(“literal”) would have required adding “const” to the declaration of func). But attempting to modify a string literal invokes undefined behavior.

    C++ was less concerned with backward compatibility, so C++ string literals are of type const char[N].

  11. Pingback: Types and printf « /etc/shadow

Comments are closed.