If the behavior you want is a truncating version of strcpy
that copies the longest initial prefix of the source string into a buffer of known size, there are multiple options for you:
You can write a tailor made function that does the job:
char *safe_strcpy(char *dest, size_t size, char *src) {
if (size > 0) {
size_t i;
for (i = 0; i < size - 1 && src[i]; i++) {
dest[i] = src[i];
}
dest[i] = '\0';
}
return dest;
}
Most BSD systems have a function strlcpy(char *dest, const char *src, size_t n);
that operates the same. The order of its arguments is confusing as n
is usually the size of the dest
array, but comes after the src
argument.
You can use strncat()
:
char *safe_strcpy(char *dest, size_t size, char *src) {
if (size > 0) {
*dest = '\0';
return strncat(dest, src, size - 1);
}
return dest;
}
You can use snprintf()
or sprintf()
, but it feels like using a hydraulic press to drive in a nail:
snprintf(dest, size, "%s", src);
Alternately:
if (size > 0) {
sprintf(dest, "%.*s", (int)(size - 1), src);
}
You can use strlen()
and memcpy()
, but this is only possible if you know that the source pointer points to a null terminated string. It is also less efficient than both of the above solutions if the source string is much longer than the destination array:
char *safe_strcpy(char *dest, size_t size, char *src) {
if (size > 0) {
size_t len = strlen(src);
if (len >= size)
len = size - 1;
memcpy(dest, src, len);
dest[len] = '\0';
}
return dest;
}
The inefficiency can be avoided with strnlen()
if available on the target system:
char *safe_strcpy(char *dest, size_t size, char *src) {
if (size > 0) {
size_t len = strnlen(src, size - 1);
memcpy(dest, src, len);
dest[len] = '\0';
}
return dest;
}
You could use strncpy()
and force null termination. This would be inefficient if the destination array is large because strncpy()
also fills the rest of the destination array with null bytes if the source string is shorter. This function's semantics are very counter-intuitive, poorly understood and error-prone. Even when used correctly, occurrences of strncpy()
are bugs waiting to bite, as the next programmer, bolder but less savvy, might alter the code and introduce them in an attempt to optimize code he does not fully understand. Play it safe: avoid this function.
Another aspect of this question is the ability for the caller to detect truncation. The above implementations of safe_strcpy
return the target pointer, as strcpy
does, hence do not provide any information to the caller. snprintf()
returns an int
representing the number of characters that would have been copied if the target array was large enough, in this case, the return value is strlen(src)
converted to int
, which allows the caller to detect truncation and other errors.
Here is another function more appropriate for composing a string from different parts:
size_t strcpy_at(char *dest, size_t size, size_t pos, const char *src) {
size_t len = strlen(src);
if (pos < size) {
size_t chunk = size - pos - 1;
if (chunk > len)
chunk = len;
memcpy(dest + pos, src, chunk);
dest[pos + chunk] = '\0';
}
return pos + len;
}
This function can be used in sequences without undefined behavior:
void say_hello(const char **names, size_t count) {
char buf[BUFSIZ];
char *p = buf;
size_t size = sizeof buf;
for (;;) {
size_t pos = strcpy_at(p, size, 0, "Hello");
for (size_t i = 0; i < count; i++) {
pos = strcpy_at(p, size, pos, " ");
pos = strcpy_at(p, size, pos, names[i]);
}
pos = strcpy_at(p, size, pos, "!");
if (pos >= size && p == buf) {
// allocate a larger buffer if required
p = malloc(size = pos + 1);
if (p != NULL)
continue;
p = buf;
}
printf("%s\n", p);
if (p != buf)
free(p);
break;
}
}
An equivalent approach for snprintf
would be useful too, passing pos
by address:
size_t snprintf_at(char *s, size_t n, size_t *ppos, const char *format, ...) {
va_list arg;
int ret;
size_t pos = *ppos;
if (pos < n) {
s += pos;
n -= pos;
} else {
s = NULL;
n = 0;
}
va_start(arg, format);
ret = snprintf(s, n, format, arg);
va_end(arg);
if (ret >= 0)
*ppos += ret;
return ret;
}
passing pos
by address instead of by value allows for snprintf_at
to return snprintf
's return value, which can be -1
in case of encoding error.
strncpy_s
– Laststrncpy
inserts null bytes if the passed maximum size is larger thanstrlen
of the passed string. Maybe you can use that. – Trisaccharidesnprintf
always null terminate? – Catherinesnprintf
always null-terminates unless the buffer size is 0 – Brokstrncpy
is the output buffer size, not the number of characters to be copied – Brokstrlen(src) < n
(and if so, what you want to happen in that case) – Brok