This is the single biggest source of confusion I've noticed while maintaining C code,
and it's time consuming and error prone to retroactively fix string buffer issues.
So here are some simple rules for using these buffers and associated functions.
snprintf & strncpy
These functions should specify the full size of the destination buffer, because snprintf will NUL terminate, and with strncpy there is no way to determine if the string was truncated without checking the last character of the buffer. Using memset, to zero the buffer before using these functions is redundant and inefficient. Here is an example of correct usage:#include <stdio.h> #include <string.h> int main(void) { char dest[5]; char src[5]="hello"; //Not NUL terminated dest[sizeof(dest)-1]='\0'; //for later check (void) strncpy(dest, src, sizeof(dest)); if (dest[sizeof(dest)-1]!='\0') printf("truncated"); dest[sizeof(dest)-1]='\0'; //should always do this int required = snprintf(dest, sizeof(dest), "%s", src); if (required >= sizeof(dest)) printf("truncated"); }
Improving sprintf & strcpy
While snprint() and strncpy() discussed above are safer than the original sprintf() and strcpy(), they can be improved upon. Alternatives to consider if available are asnprintf, strscpy or to use variable length string buffers. A good summary of the behaviours of these functions is discussed in designing a better strcpy(). That mentions strscpy() as a better interface, and strscpy advantages are presented in the discussion on the kernel moving away from strlcpy() to using strscpy() (for performance and robustness reasons). Note related functions like strcat also can be improved on. strcat is inefficient as it continually has to search for NUL, so instead consider usingstpcpyinstead of both strcpy and strcat. Finally which of the above mentioned functions to use depends on conditions like, is the source NUL terminated, is the dest buffer oversized (so would be inefficient to pad with NULs), is the dest buffer guaranteed to be large enough, do you know the source and/or dest sizes already. Often it may be simpler or more efficient to fall back to memcpy as was done in buffer_or_output() for example, where we're appending to a string in a large buffer.
static allocation
It's also worth noting how string buffers are allocated by the compiler. I.E. beware of sizeof on stringschar* string1="12345678"; char string2[]="12345678"; char string3[8]="12345678"; /* C99 supports excluding NUL, C++ does not. */ #define string4 "12345678" //sizeof(string1) = 4 (sizeof pointer!) //sizeof(string2) = 9 (NUL char counted!) //sizeof(string3) = 8 (Not NUL terminated!) //sizeof(string4) = 9 (NUL char counted!)In the string declarations above note that string1 is readonly, even though it's not declared const. I.E. you will get a runtime error rather than a compile time one if you try to modify it. string2 and string3 are writable as the compiler will generate code to copy the string from a readonly data segment to the array at runtime.
dynamic allocation
It's worth noting how variable length arrays are handled since they're supported since gcc 2.95 and by the C99 standard.char* string1=malloc(var); char string2[var]; //sizeof(string1) = 4 (sizeof pointer) //sizeof(string2) = value of var (determined at run time!)
© Sep 17 2008