Skip to main content
16 of 26
Fix includes

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. First I added code to the macro, but it proved to have some problems (at least it didn't compile, so no dangerous bugs). Now it's an extern function enclosed in a simple macro.

Now the code relies less on GCC extensions.

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:

sbprintf.h:

#ifndef ALX_STDIO_PRINTF_SBPRINTF_H
#define ALX_STDIO_PRINTF_SBPRINTF_H


#include <stddef.h>

#include "libalx/base/assert/assert.h"


/*
 * int  alx_sbprintf__(char buff[restrict], int *restrict written,
 *              const char *restrict format, ...);
 */
#define alx_sbprintf__(buff, written, fmt, ...) (                       \
{                                                                       \
                                                                        \
        alx_static_assert_array(buff);                                  \
        alx_sbprintf(buff, written, sizeof(buff), fmt, ##__VA_ARGS__);  \
}                                                                       \
)


int     alx_sbprintf(char buff[restrict], int *written, size_t nmemb,
                        char *restrict format, ...);


#endif      /* libalx/base/stdio/printf/sbprintf.h */

sbprintf.c:

#include "libalx/base/stdio/printf/sbprintf.h"

#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>


int     alx_sbprintf(char buff[restrict], int *written, size_t nmemb,
                        char *restrict format, ...)
{
        va_list ap;
        int len;

        va_start(ap, format);
        len     = vsnprintf(buff, nmemb, format, ap);
        va_end(ap);

        if (written != NULL)
                *written = len;

        if (len < 0)
                goto err;
        if ((unsigned)len >= nmemb)
                goto trunc;

        return  0;
err:
        return  -errno;
trunc:
        if (written)
                *written = nmemb - 1;
        errno   = ENOMEM;
        return  ENOMEM;
}