Search code examples
cprintfsize-t

Why does snprintf() take a size_t size limit, but returns an int number of chars printed?


The venerable snprintf() function...

int snprintf( char *restrict buffer, size_t bufsz, const char *restrict format, ... );
  • returns the number of characters it prints, or rather, the number it would have printed had it not been for the buffer size limit.
  • takes the size of the buffer in characters/bytes.

How does it make sense for the buffer size to be size_t, but for the return type to be only an int?

If snprintf() is supposed to be able to print more than INT_MAX characters into the buffer, surely it must return an ssize_t or a size_t with (size_t) - 1 indicating an error, right?

And if it is not supposed to be able to print more than INT_MAX characters, why is bufsz a size_t rather than, say, an unsigned or an int? Or - is it at least officially constrained to hold values no larger than INT_MAX?


Solution

  • How does it make sense for the buffer size to be size_t, but for the return type to be only an int?

    The official C99 rationale document does not discuss these particular considerations, but presumably it's for consistency and (separate) ideological reasons:

    • all of the printf-family functions return an int with substantially the same significance. This was defined (for the original printf, fprintf, and sprintf) well before size_t was invented.

    • type size_t is in some sense the correct type for conveying sizes and lengths, so it was used for the second arguments to snprintf and vsnprintf when those were introduced (along with size_t itself) in C99.

    If snprintf() is supposed to be able to print more than INT_MAX characters into the buffer, surely it must return an ssize_t or a size_t with (size_t) - 1 indicating an error, right?

    That would be a more internally-consistent design choice, but nope. Consistency across the function family seems to have been chosen instead. Note that none of the functions in this family have documented limits on the number of characters they can output, and their general specification implies that there is no inherent limit. Thus, they all suffer from the same issue with very long outputs.

    And if it is not supposed to be able to print more than INT_MAX characters, why is bufsz a size_t rather than, say, an unsigned or an int? Or - is it at least officially constrained to hold values no larger than INT_MAX?

    There is no documented constraint on the value of the second argument, other than the implicit one that it must be representable as a size_t. Not even in the latest version of the standard. But note that there is also nothing that says that type int cannot represent all the values that are representable by size_t (though indeed it can't in most implementations).

    So yes, implementations will have trouble behaving according to the specifications when very large data are output via these functions, where "very large" is implementation-dependent. As a practical matter, then, one should not rely on using them to emit very large outputs in a single call (unless one intends to ignore the return value).