C: figuring out % format specifiers for typedefs without gcc warnings?

Viewed 255

One of the problems I encounter when trying to use typedef'd variables in C is figuring out what the actual primitive type is that has been typedef'd so I can use the appropriate the % specifier when trying to print its value using printf. Most of the time I have to refer to the warnings produced by gcc. For example, when using the stat system call, the struct returned has lots of members with different typedef'd types like dev_t, ino_t, mode_t, etc, and I usually have to guess what the format specifier might be and then the compiler warns me and I correct it:

stat_demo.c: In function ‘main’:
stat_demo.c:46:45: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘__blksize_t’ {aka ‘long int’} [-Wformat=]
   46 |     printf("block size for file system IO: %d\n", s->st_blksize);
      |                                            ~^     ~~~~~~~~~~~~~
      |                                             |      |
      |                                             int    __blksize_t {aka long int}
      |                                            %ld

Is this the way most people work with typedefs when printing them ? Doesn't this defeat the point since you need to know what the actual primtive being typedef'd is? Is there a better way where the actual type is completely abstracted ?

2 Answers

There is no system-independent definition of these types. POSIX says only that they are "arithmetic types of an appropriate length". So even if you infer from the compiler warnings that dev_t is int on your system, you will have problems when you move to some other system where it could be unsigned short or long int or even, theoretically, float.

Normally you deal with this by casting, so that what's passed to printf is at least of a known type. uintmax_t is convenient in this regard since it should certainly be large enough, and it has a format specifier %ju. So you could write:

printf("block size for file system IO: %ju\n", (uintmax_t)(s->st_blksize));

Doesn't this defeat the point?

Yes, you're right, it does.

Is there a better way where the actual type is completely abstracted?

No, not really.

The fundamental problem is that if you want a proper, abstract, type-safe way of printing arbitrary things, C's printf isn't it. One way to do it is with C++ ostreams — since any type can overload operator<< — although C++ ostreams aren't necessarily perfect, either.

Since there aren't perfect answers here, it ends up being a pretty subjective question. Here's what I do, for what it's worth:

  1. First of all, I don't use that many typedefs, and certainly not for numeric types.
  2. If I have a size_t, I print it with %zu.
  3. For most other typedefs — and, in particular, for something like off_t — I cast to unsigned long, and print with %lu.

Number 3, there, is decidedly imperfect, and will theoretically nullify someone's attempt to achieve an even larger off_t type, perhaps one that's equivalent to long long. A safer way — I should get in the habit of doing this — would be to cast to intmax_t or uintmax_t, and print with %jd or %ju.

The "official" way to print the fixed-width types (if you know that's what you've got) is to use one of the macros from <inttypes.h>, like this:

uint32_t n;
printf("%" PRIX32 "\n", n);

I thought there were PRIxxx macros for filesystem-related typedefs like off_t, but maybe not.

See also How should I print types like off_t and size_t?

Related