Skip to main content
11 of 26
ptrdiff_t

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], ptrdiff_t *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_;                                                           \
}                                                                       \
)