32

On a cross platform c/c++ project (Win32, Linux, OSX), I need to use the *printf functions to print some variables of type size_t. In some environments size_t's are 8 bytes and on others they are 4. On glibc I have %zd, and on Win32 I can use %Id. Is there an elegant way to handle this?

chills42
  • 13,026
  • 2
  • 41
  • 75
twk
  • 15,310
  • 21
  • 68
  • 95

10 Answers10

20

The PRIuPTR macro (from <inttypes.h>) defines a decimal format for uintptr_t, which should always be large enough that you can cast a size_t to it without truncating, e.g.

fprintf(stream, "Your size_t var has value %" PRIuPTR ".", (uintptr_t) your_var);
tzot
  • 81,264
  • 25
  • 129
  • 197
finnw
  • 45,253
  • 22
  • 134
  • 212
  • finnw, you better change your PRIuPTR into `PRIuPTR` and prefix your "fprintf" line with 4 spaces, so that they become formatted as code and there won't be a confusion between PRIuPTR and PRluPTR (as seen here). – tzot Oct 06 '08 at 15:37
  • @ΤΖΩΤΖΙΟY, I added the prefix, but what do the backticks around PRIuPTR do? I can't see any difference. – finnw Oct 06 '08 at 16:49
  • Backticks let you put code (or other stuff you don't want formatted by markdown) inline with text. The markdown formatter often gets confused by things like underscores in identifiers. Backticking inline code helps with this. – Michael Burr Oct 06 '08 at 17:15
  • Backticks don't seem to work in comments. Don't know why. Also no preview in comments. – Rhythmic Fistman Oct 10 '08 at 21:01
  • Backticks also allow the reader to see the difference between Int and lnt :) – tzot Oct 15 '08 at 16:47
  • 4
    If it is C++ then do not forget define __STDC_FORMAT_MACROS before including . – Sergey Shandar May 10 '11 at 06:31
  • Visual Studio 2013 now supports inttypes.h – BSalita May 07 '14 at 18:04
13

There are really two questions here. The first question is what the correct printf specifier string for the three platforms is. Note that size_t is an unsigned type.

On Windows, use "%Iu".

On Linux and OSX, use "%zu".

The second question is how to support multiple platforms, given that things like format strings might be different on each platform. As other people have pointed out, using #ifdef gets ugly quickly.

Instead, write a separate makefile or project file for each target platform. Then refer to the specifier by some macro name in your source files, defining the macro appropriately in each makefile. In particular, both GCC and Visual Studio accept a 'D' switch to define macros on the command line.

If your build system is very complicated (multiple build options, generated sources, etc.), maintaining 3 separate makefiles might get unwieldly, and you are going to have to use some kind of advanced build system like CMake or the GNU autotools. But the basic principle is the same-- use the build system to define platform-specific macros instead of putting platform-detection logic in your source files.

ipmcc
  • 28,584
  • 4
  • 78
  • 135
  • 2
    Good for pointing out `%Iu` (win) and `%zu` (mac/linux) which are more officially correct than what the question suggested. Windows officially defines the `_WIN32` macro, and I personally have found development easier to rely on small, concentrated `#ifdef` claused based on this macro rather than per-platform makefiles. Although I use Visual Studio & Xcode, which effectively have their own version of makefiles as well. The difference is in minimizing number of macro definitions and `#ifdef` cases. – Tyler Jul 18 '14 at 16:26
  • According to 'cppcheck', on Windows we could use both formats. – alcor Feb 06 '15 at 15:43
8

The only thing I can think of, is the typical:

#ifdef __WIN32__ // or whatever
#define SSIZET_FMT "%ld"
#else
#define SSIZET_FMT "%zd"
#endif

and then taking advantage of constant folding:

fprintf(stream, "Your size_t var has value " SSIZET_FMT ".", your_var);
tzot
  • 81,264
  • 25
  • 129
  • 197
4

Dan Saks wrote an article in Embedded Systems Design which covered this matter. According to Dan, %zu is the standard way, but few compilers supported this. As an alternative, he recommended using %lu together with an explicit cast of the argument to unsigned long:

size_t n;
...
printf("%lu", (unsigned long)n);
  • 1
    That's not so great on systems that use the LLP64 programming model, such as 64-bit Windows. – bk1e Oct 07 '08 at 05:08
  • %zu is a C99 invention. Those compilers are indeed rare. C++ doesn't have the problem to start with. – MSalters Oct 07 '08 at 12:02
  • 1
    %zu has nothing to do with the compiler and everything to do with the standard libraries... – plinth Nov 03 '08 at 13:28
  • %lu and unsigned long are only a problem on 64-bit systems when you want to be able to display values exceeding 2^32-1. Otherwise, always cast to unsigned long long (works just as fine for 32-bit), and use %llu. – paniq Mar 16 '11 at 19:09
  • [Archived version](http://web.archive.org/web/20071023080758/http://www.embedded.com/columns/programmingpointers/201803576?pgno=2) of the article by Dan Saks. – Nathan Mills Feb 20 '19 at 10:24
2

Use boost::format. It's typesafe, so it'll print size_t correctly with %d, also you don't need to remember to put c_str() on std::strings when using it, and even if you pass a number to %s or vice versa, it'll work.

Lev
  • 5,897
  • 5
  • 24
  • 29
1

You just have to find an integer type with the largest storage class, cast the value to it, and then use the appropriate format string for the larger type. Note this solution will work for any type (ptrdiff_t, etc.), not just size_t.

What you want to use is uintmax_t and the format macro PRIuMAX. For Visual C++, you are going to need to download c99-compatible stdint.h and inttypes.h headers, because Microsoft doesn't provide them.

Also see

http://www.embedded.com/columns/technicalinsights/204700432

This article corrects the mistakes in the article quoted by Frederico.

1

I don't know of any satisfying solution, but you might consider a specialized function to format size_t items to a string, and print the string.

(Alternatively, if you can get away with it, boost::format handles this kind of thing with ease.)

Head Geek
  • 33,955
  • 20
  • 72
  • 83
0

My choice for that problem is to simply cast the size_t argument to unsigned long and use %lu everywhere - this of course only where values are not expected to exceed 2^32-1. If this is too short for you, you could always cast to unsigned long long and format it as %llu.

Either way, your strings will never be awkward.

paniq
  • 1,019
  • 1
  • 11
  • 18
0

Option 1:

Since on most (if not all?) systems, the PRIuPTR printf format string from is also long enough to hold a size_t type, I recommend using the following defines for size_t printf format strings.

However, it is important that you verify this will work for your particular architecture (compiler, hardware, etc), as the standard does not enforce this.

#include <inttypes.h>

// Printf format strings for `size_t` variable types.
#define PRIdSZT PRIdPTR
#define PRIiSZT PRIiPTR
#define PRIoSZT PRIoPTR
#define PRIuSZT PRIuPTR
#define PRIxSZT PRIxPTR
#define PRIXSZT PRIXPTR

Example usage:

size_t my_variable;
printf("%" PRIuSZT "\n", my_variable);

Option 2:

Where possible, however, just use the %zu "z" length specifier, as shown here, for size_t types:

enter image description here

Example usage:

size_t my_variable;
printf("%zu\n", my_variable);

On some systems, however, such as STM32 microcontrollers using gcc as the compiler, the %z length specifier isn't necessarily implemented, and doing something like printf("%zu\n", my_size_t_num); may simply end up printing out a literal "%zu" (I personally tested this and found it to be true) instead of the value of your size_t variable.

Option 3:

Where you need it to be absolutely guaranteed to work, however, or where you aren't sure about your particular architecture, just cast and print as a uint64_t and be done, as this is guaranteed to work, but requires the extra step of casting.

Example usage:

#include <stdint.h>    // for uint64_t
#include <inttypes.h>  // for PRIu64

size_t my_variable;
printf("%" PRIu64 "\n", (uint64_t)my_variable);

Sources Cited:

  1. http://www.cplusplus.com/reference/cstdio/printf/
  2. http://www.cplusplus.com/reference/cinttypes/
  3. http://www.cplusplus.com/reference/cstdint/
Gabriel Staples
  • 11,777
  • 3
  • 74
  • 108
-1

size_t is an unsigned type of at least 16 bits. Widths of 32 and 64 are often seen.

printf("%zu\n", some_size_t_object); // Standard since C99

Above is the best way going forward, yet if code needs to also port to pre-C99 platforms, covert the value to some wide type. unsigned long is reasonable candidate yet may be lacking.

// OK, yet insufficient with large sizes > ULONG_MAX
printf("%lu\n", (unsigned long) some_size_t_object); 

or with conditional code

#ifdef ULLONG_MAX
  printf("%llu\n", (unsigned long long) some_size_t_object); 
#else
  printf("%lu\n", (unsigned long) some_size_t_object); 
#endif

Lastly consider double. It is a bit inefficient yet should handle all ancient and new platforms until about the years 2030-2040 considering Moore's law when double may lack a precise result.

printf("%.0f\n", (double) some_size_t_object);
chux - Reinstate Monica
  • 113,725
  • 11
  • 107
  • 213
  • Doubles leave gaps above 32 bits, so effectively they will often give a wrong result for large values. – rubenvb Jul 31 '17 at 21:28
  • @rubenvb Typical double has 53 bit of precision and so will leave gaps at about 9,000,000 gigabytes, not 32 as suggested. Even the pedantic minimum C spec allows 33+ bits of precision. Curious as to where your 32-bit precession comes from? – chux - Reinstate Monica Jul 31 '17 at 22:07
  • Well, the 32 comes from the fact there is no integer size between that and 64, and as you say, 64 bits is too much for double. So yes, gaps when using this for sizes might not be common, but the question (title) here is how to print a size_t. Using double is not a good general solution. Neither will it perform well... – rubenvb Aug 02 '17 at 14:59
  • @rubenvb FWIW, I have worked with `(u)int48_t` and of course that is not a common type. For printing size of of objects, 53 bits is sufficient for the next decade and using `double` remains portable for that. Yet to your point, OP did say "print some variables of type size_t", so those values could exceed the size of a single object due to various calculations. Thus this does not meet OP's precise goal. The performance issue is at best minor - at worse simple premature optimization. OP's goal is cross-platform, not speed. Curious - what solution do you see as best for cross-platform? – chux - Reinstate Monica Aug 02 '17 at 15:12
  • using a decent C99 compiler/library that supports the proper printf format strings/macros. Barring that, #define'ing them yourself. Really, it's Visual Studio that makes this more difficult than need be. – rubenvb Aug 02 '17 at 15:54
  • 1
    @rubenvb Fair enough - I took the cross-platform tag as also seeking a C89 - C11 solution - without obliging the compiler to change. Agree using a recent compliant complier solves this issue. Thanks for the feedback of which I mostly agree. – chux - Reinstate Monica Aug 02 '17 at 16:06
  • @rubenvb %zd and %zu works in Visual Studio 2013 and above: https://stackoverflow.com/questions/15610053/correct-printf-format-specifier-for-size-t-zu-or-iu – Étienne Jul 08 '19 at 12:28