Your code invokes undefined behavior because you pass the destination buffer as one of the source strings of your snprintf()
format. This is not supported:
7.21.6.5 The snprintf
function
Synopsis
#include <stdio.h>
int snprintf(char * restrict s, size_t n,
const char * restrict format, ...);
Description
The snprintf
function is equivalent to fprintf
, except that the output is written into an array (specified by argument s
) rather than to a stream. If n
is zero, nothing is written, and s
may be a null pointer. Otherwise, output characters beyond the n-1
st are discarded rather than being written to the array, and a null character is written at the end of the characters actually written into the array. If copying takes place between objects that overlap, the behavior is undefined.
(emphasis mine).
The implementation of snprintf
differs between Ubuntu (glibc) and OS/X (Apple libc, based on BSD sources). The behavior differs and cannot be relied upon as it is undefined in all cases.
You can implement safe_strcat()
this way:
#include <string.h>
char *safe_strcat(char *dest, size_t size, const char *src) {
char *p = memchr(dest, '\0', size);
if (p != NULL) {
strncat(p, src, size - (p - dest) - 1);
}
return dest;
}
Notes:
- do not call this function
safe_strncat()
, it is really a safe version of strcat()
.
- pass the size of the destination array after the destination pointer itself, not after the source pointer.
- returning the destination pointer does not allow the caller to detect truncation. You could instead return the length of the result if the destination array had been large enough, like
snprintf()
, you it would still not tell the caller if the destination was not null terminated before the call (for safe_strcat
and safe_strncat
).
You can use the same model for safe versions of strcpy
, strncat
and strncpy
(but not implementing strncpy()
's counter-intuitive semantics):
char *safe_strcpy(char *dest, size_t size, const char *src) {
if (size > 0) {
*dest = '\0';
strncat(dest, src, size - 1);
}
return dest;
}
char *safe_strncat(char *dest, size_t size, const char *src, size_t n) {
char *p = memchr(dest, '\0', size);
if (p != NULL) {
if (n > size - (p - dest) - 1)
n = size - (p - dest) - 1;
strncat(p, src, n);
}
return dest;
}
char *safe_strncpy(char *dest, size_t size, const char *src, size_t n) {
if (size > 0) {
if (n > size - 1)
n = size - 1;
*dest = '\0';
strncat(dest, src, n);
}
return dest;
}