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
isNULL
it doesn't write into it. - The return value is
true
when the string is truncated andfalse
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
isNULL
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
), andwritten
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_; \
} \
)