C functions that should be avoided (part 2) - strncpy
This is the second post on functions that should be avoided. I covered atoi and gets in a previous post.
strncpy
char *strncpy(char * restrict s1, const char * restrict s2, size_t n);
Many beginners think of strncpy
as a “safe” or “safer” version of strcpy. Unfortunately, while it does help prevent writing past the end of the destination buffer, it still isn’t particularly safe.
From the standard1:
The strncpy function copies not more than n characters (characters that follow a null character are not copied) from the array pointed to by s2 to the array pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined.
If the array pointed to by s2 is a string that is shorter than n characters, null characters are appended to the copy in the array pointed to by s1, until n characters in all have been written.
The strncpy function returns the value of s1.
The biggest problem with this function, is that even if you appear to use it correctly, you might not get a string. Consider:
#include <stdio.h>
#include <string.h>
int main(void) {
char dest[4];
char *src = "This string will not fit in dest";
strncpy(dest, src, sizeof dest); // okay, no undefined behavior.
printf("%s\n", dest); // undefined behavior.
return 0;
}
Here strncpy
fills dest
’s four elements with ‘T’, ‘h’, ‘i’, ‘s’, but the problem is that we don’t have a string as it’s not null terminated. Using it with any function that expects a string, such as printf, results in undefined behavior. We could mitigate the undefined behavior by setting the last element of dest
to the null character explicitly, but we still have no way of knowing if the copy wasn’t complete, without checking the length of the original source string.
OpenBSD provided a function not found in standard C called strlcpy
, which is now available on many systems:
size_t strlcpy(char *dst, const char *src, size_t size);
The advantages of strlcpy
are:
- The destination is null terminated even if it wasn’t big enough to fit all of the source string.
- The return value is the length of the string it tried to copy (the source string length) so you can detect truncation.
The safer code with strlcpy
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char dest[4];
char *src = "This string will not fit in dest";
size_t len = strlcpy(dest, src, sizeof dest);
if (len >= sizeof dest) {
// oh no, truncated.
exit(EXIT_FAILURE);
}
printf("%s\n", dest); // guaranteed safe.
return 0;
}
However, in my opinion, strlcpy
isn’t particularly useful in most cases, because you can accomplish the same thing just using standard C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char dest[4];
char *src = "This string will not fit in dest";
if (strlen(src) >= sizeof dest)
// oh no, truncated.
exit(EXIT_FAILURE);
}
strcpy(dest, src); // guaranteed safe due to check.
printf("%s\n", dest); // guaranteed safe.
return 0;
}
The only reason strlcpy
would be useful is if you are detecting truncation but still want to use the destination result anyway.
One final reason not to use strncpy
, as if the above isn’t enough, is that strncpy
will pad the rest of the destination buffer with nulls. This is usually a complete waste of time:
char dest[640*1024]; // ought to be enough for anybody
strncpy(dest, "hello", sizeof dest);
Here the strncpy
function writes “hello” followed by 655354 superfluous null bytes.
It has been demonstrated that strncpy
is not suitable for copying strings. However, the one thing it does excel at is copying data into fixed width, zero padded, records.
C11 provides new, safer, library functions called strcpy_s
2 and strncpy_s
3 as part of its optional runtime bounds checking interfaces (Annex K). However, these functions are not currently widely implemented so I will not cover them here.
References