How to print types of unknown size like ino_t?

fuz picture fuz · Jul 19, 2014 · Viewed 10k times · Source

I often experience situations where I want to print with printf the value of an integer type of implementation-defined size (like ino_t or time_t). Right now, I use a pattern like this for this:

#include <inttypes.h>

ino_t ino; /* variable of unknown size */
printf("%" PRIuMAX, (uintmax_t)ino);

This approach works so far but it has a couple of disadvantages:

  • I have to know whether the type I'm trying print is signed or unsigned.
  • I have to use a type cast that possibly enlarges my code.

Is there a better strategy?

Answer

Keith Thompson picture Keith Thompson · Jul 21, 2014
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
/* ... */
printf("%" PRIuMAX, (uintmax_t)ino);

That will certainly work (with some provisos; see below), but I'd use:

printf("%ju", (uintmax_t)ino);

The j length modifier

Specifies that a following d, i, o, u, x, or X conversion specifier applies to an intmax_t or uintmax_t argument; or that a following n conversion specifier applies to a pointer to an intmax_t argument.

There are also z and t modifiers for size_t and ptrdiff_t (and their corresponding signed/unsigned types), respectively.

And personally, I find the format string macros defined in <inttypes.h> ugly and difficult to remember, which is why I prefer "%ju" or "%jd".

As you mentioned, it's helpful to know whether the type (ino_t in this case) is signed or unsigned. If you don't happen to know that, it's possible to figure it out:

#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>

#define IS_SIGNED(type) ((type)-1 < (type)0)
#define DECIMAL_FORMAT(type) (IS_SIGNED(type) ? "%jd" : "%ju")
#define CONVERT_TO_MAX(type, value) \
    (IS_SIGNED(type) ? (intmax_t)(value) : (uintmax_t)(value))
#define PRINT_VALUE(type, value) \
    (printf(DECIMAL_FORMAT(type), CONVERT_TO_MAX(type, (value))))

int main(void) {
    ino_t ino = 42;
    PRINT_VALUE(ino_t, ino);
    putchar('\n');
}

though that may be overkill. If you're sure the type is narrower than 64 bits, you can convert the value to intmax_t, and the value will be preserved. Or you can use uintmax_t and get well-defined results for all values, though printing -1 as 18446744073709551615 (264-1) may be a bit confusing.

All of this works only if your C implementation supports <stdint.h> and the j length modifier for printf -- i.e., if it supports C99. Not all compilers do so (coughMicrosoftcough). For C90, the widest integer types are long and unsigned long, and you can convert to those and use "%ld" and/or "%lu". You can theoretically test for C99 compliance using the __STDC_VERSION__ predefined macro -- though some pre-C99 compilers might still support types wider than long and unsigned long as an extension.