%ld format conversion for portability

wallyk picture wallyk · Jun 5, 2013 · Viewed 13.6k times · Source

I have a code base which is intended to compile without warnings and run without any glitches on multiple architectures, all x86: MSDOS, Windows 32 console mode, Windows 32 GUI mode, Linux 32, and Linux 64.

Before adding support of Linux 64, this was easy. Almost all data were declared as either int or from the typedefs for BYTE, WORD, and DWORD:

typedef unsigned char   BYTE;
typedef unsigned short  WORD;
typedef unsigned long   DWORD;

Upon adding 64-bit gcc support, DWORD needed a little tweaking to remain as a 32 bit value since it represented stored data:

// to compile DWORDs as 32 bits on 64-bit machines:
#if __x86_64__
 typedef unsigned int    DWORD;
#else
 typedef unsigned long   DWORD;
#endif

And this worked nicely on all environments:

DWORD   data;
printf ("%lu", data);

However, gcc -Wall now complains about format conversions:

warning: format ‘%ld’ expects argument of type ‘long int’, but argument
         1 has type ‘DWORD {aka unsigned int}’ [-Wformat]

Due to the extensive formatting this code does—thousands of lines of output formatting—I'd rather not retrofit a type specific formatter. A similar question was answered by using the z modifier:

printf ("%zu", data);

But that makes Turbo C on MSDOS and Win32 console do something odd: It shows the conversion specification %zu as output instead of converting anything.

Is there a cleaner way to handle the variability of the type in a way that goes with the grain of printf and the fundamental data types?

Answer

zwol picture zwol · Jun 5, 2013

I think your least-bad available option is to borrow conceptually from <inttypes.h>:

#ifdef _LP64
#define PRIdword  "d"
#define PRIudword "u"
#else
#define PRIdword  "ld"
#define PRIudword "lu"
#endif

and then

DWORD data;
printf("%"PRIdword, data);

This makes use of string constant concatenation, which all C90-compliant compilers should support. Note that the correct macro to test is _LP64, not __x86_64__; that way it will Just Work when you go to port to some other LP64 system, or to the shiny new "x32" mode of Linux/x86-64 (32-bit pointers, wide registers).

It might not be a bad idea to invest in a wholesale conversion to the <stdint.h> types, but that won't get you out of this sort of thing, you'd just be writing

int32_t data;
printf("%"PRId32, data);

instead, and as far as I know most Windows compilers still don't have <inttypes.h>, sigh.

In case you're wondering, the % is not inside the macro so you can put format adjusters in if you want:

printf("%-32"PRIdword, data);