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. 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
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:
sbprintf.h
:
#ifndef ALX_STDIO_PRINTF_SBPRINTF_H
#define ALX_STDIO_PRINTF_SBPRINTF_H
#include "libalx/base/assert/assert.h"
#include "libalx/base/stdio/printf/swnprintf.h"
/*
* int sbprintf(char buff[restrict], ptrdiff_t *restrict written,
* const char *restrict format, ...);
*/
#define sbprintf(buff, written, fmt, ...) ( \
{ \
\
alx_static_assert_array(buff); \
swnprintf(buff, written, sizeof(buff), fmt, ##__VA_ARGS__); \
} \
)
#endif /* libalx/base/stdio/printf/sbprintf.h */
swnprintf.h
:
#ifndef ALX_STDIO_PRINTF_SWNPRINTF_H
#define ALX_STDIO_PRINTF_SWNPRINTF_H
#include <stddef.h>
int swnprintf(char str[restrict], ptrdiff_t *restrict written, ptrdiff_t nmemb,
const char *restrict format, ...);
#endif /* libalx/base/stdio/printf/swnprintf.h */
swnprintf.c
:
#include "libalx/base/stdio/printf/swnprintf.h"
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
int swnprintf(char str[restrict], ptrdiff_t *restrict written, ptrdiff_t nmemb,
const char *restrict format, ...)
{
va_list ap;
int len;
if (nmemb < 0)
goto neg;
va_start(ap, format);
len = vsnprintf(str, 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;
neg:
errno = EOVERFLOW;
return -EOVERFLOW;
}