Skip to main content
9 of 26
actual length

This is the code that Toby Speight suggested in his answer:

/*
 * int  sbprintf(char buff[restrict], int *restrict written,
 *                              const char *restrict format, ...);
 */
#define sbprintf(buff, written, format, ...)    (                       \
{                                                                       \
        int     len_;                                                   \
                                                                        \
        alx_static_assert_array(buff);                                  \
                                                                        \
        len_    = snprintf((buff), sizeof(buff), format, ##__VA_ARGS__);\
        if (written != NULL)                                            \
                *written = len_;                                        \
        len_ >= (int)sizeof(buff);                                      \
}                                                                       \
)

I have tested it and works as expected:

  • If written is NULL it doesn't write into it.
  • The return value is true when the string is truncated and false otherwise.
  • It doesn't compile if buff is not an array.
  • It accepts a variable number of arguments after format, including no arguments.

The comment above the macro is the prototype that a user should see, to better understand the usage, although a real function with that prototype wouldn't work because of the array decaying to a pointer.


Comments about the style:

I tried to follow the Linux Kernel Coding Style, but there have been exceptions:

if (written != NULL) is used instead of if (written) to avoid the compiler complaining:

main.c:22:23: error: the address of ‘w1’ will always evaluate as ‘true’ [-Werror=address]
  if (alx_sbprintf(b1, &w1, test))
                       ^
.../libalx/base/stdio/sbprintf.h:36:6: note: in definition of macro ‘alx_sbprintf’
  if (written)       \
      ^~~~~~~

EDIT:

Given that snprintf sets errno (at least in POSIX), it would be good to set errno to ENOMEM on truncation.


EDIT:

This version includes the improvements suggested by Toby Speight and chux, as well as setting errno on error.

The code is GCC (or Clang) specific. I doubt it's doable (with the same level of safety and clarity) in standard C, although a standard solution would be nice.

Properties:

  • If written is NULL it doesn't write into it.
  • It doesn't compile if buff is not an array.
  • It accepts a variable number of arguments after format, including no arguments.
  • Sets errno on any error.
  • If there's a snprintf internal error, the error code is negative (-errno), and written is also negative.
  • If the string is truncated, the error code is positive (ENOMEM).
  • If the error code is negative, the string should not be trusted; if it's positive, it's been truncated, but it's a valid string.

Code:

/*
 * int  alx_sbprintf(char buff[restrict], int *restrict written,
 *              const char *restrict format, ...);
 */
#define alx_sbprintf(buff, written, format, ...)        (               \
{                                                                       \
        int     len_;                                                   \
        int     err_;                                                   \
                                                                        \
        alx_static_assert_array(buff);                                  \
        err_    = 0;                                                    \
                                                                        \
        len_    = snprintf(buff, sizeof(buff), format, ##__VA_ARGS__);  \
        if (written != NULL)                                            \
                *written = len_;                                        \
                                                                        \
        if (len_ < 0) {                                                 \
                err_    = -errno;                                       \
                goto ret_;                                              \
        }                                                               \
        if ((unsigned)len_ >= sizeof(buff)) {                           \
                errno   = ENOMEM;                                       \
                err_    = ENOMEM;                                       \
                goto ret_;                                              \
        }                                                               \
ret_:                                                                   \
        err_;                                                           \
}                                                                       \
)

EDIT:

It would be more useful to store the actual length of the string, than the one returned by snprintf, so that the user can use it directly without further tests. So the last if above would be:

        if ((unsigned)len_ >= sizeof(buff)) {                           \
                *written = sizeof(buff) - 1;                            \
                errno   = ENOMEM;                                       \
                err_    = ENOMEM;                                       \
                goto ret_;                                              \
        }                                                               \