How to use “zd” specifier with `printf()`?

printf with a "%zd" format expects an argument of the signed type that corresponds to the unsigned type size_t.

Standard C doesn’t provide a name for this type or a good way to determine what it is. If size_t is a typedef for unsigned long, for example, then "%zd" expects an argument of type long, but that’s not a portable assumption.

The standard requires that corresponding signed and unsigned types use the same representation for the non-negative values that are representable in both types. A footnote says that this is meant to imply that they’re interchangeable as function arguments. So this:

size_t s = 42;
printf("s = %zd\n", s);

should work, and should print “42”. It will interpret the value 42, of the unsigned type size_t, as if it were of the corresponding signed type. But there’s really no good reason to do that, since "%zu" is also correct and well defined, without resorting to additional language rules. And "%zu" works for all values of type size_t, including those outside the range of the corresponding signed type.

Finally, POSIX defines a type ssize_t in the headers <unistd.h> and <sys/types.h>. Though POSIX doesn’t explicitly say so, it’s likely that ssize_t will be the signed type corresponding to size_t.
So if you’re writing POSIX-specific code, "%zd" is (probably) the correct format for printing values of type ssize_t.

UPDATE: POSIX explicitly says that ssize_t isn’t necessarily the signed version of size_t, so it’s unwise to write code that assumes that it is:

ssize_t

This is intended to be a signed analog of size_t. The wording is such
that an implementation may either choose to use a longer type or
simply to use the signed version of the type that underlies size_t.
All functions that return ssize_t (read() and write()) describe as
“implementation-defined” the result of an input exceeding {SSIZE_MAX}.
It is recognized that some implementations might have ints that are
smaller than size_t. A conforming application would be constrained not
to perform I/O in pieces larger than {SSIZE_MAX}, but a conforming
application using extensions would be able to use the full range if
the implementation provided an extended range, while still having a
single type-compatible interface. The symbols size_t and ssize_t are
also required in <unistd.h> to minimize the changes needed for calls
to read() and write(). Implementors are reminded that it must be
possible to include both <sys/types.h> and <unistd.h> in the same
program (in either order) without error.

Leave a Comment