strcpy, strcat and sprintf vs strncpy, strncat and snprintf

It is quite often we see that the usage of strcpy, strcat and sprintf is challenged in most of the static code analyzers such as Coverity®. Instead these tools suggest to use strncpy, strncat and snprintf respectively.

First we need to understand that replacing them is not the panacea for all such occurrences. Sometimes software developers think that this would curtain the possible defects created by the programmer and would lead to several performance issues in the longer run. As an example, if we perform a snprintf over sprintf, we might expect to limit the program to correctly null-pointer the memory allocation without exceeding the limit. This would probably avoid the program being crashed when there is a long formation of string directed in to the program. Think of a situation that the input to the snprintf exceeds the limit allocated. But the programmer will never know that there were such an occurrence and program will produce inaccurate behaviors such as inability in retrieving the original input.

But it does not mean that using of snprintf over sprintf gives no values to your effort. There are some good reasons why static code analyzers suggest this. You can find some virtues of this here.

I am listing some of the moot but important facts about the usage of strncpy, strncat and snprintf as follows.

 

1. No ‘-1’ is needed with the snprintf size argument.

It is evident that the most of the programmers tend to limit memory one less than the actual memory of character array to preserve the allocation of null character (‘\0’).

char msg[100] = {0};

snprintf(msg, sizeof(msg)-1, “‘-1’ for the usage of ‘-1′”);

But for snprintf, this gives no benefits as the resulting string will always be one shorter than the allocation(n). It means snprintf will discard all the characters beyond (n-1) and preserve the n’th location for the null character. Instead of the above, the following would be sufficient.

snprintf(msg, sizeof(msg), “I give you now ‘+1′”);

 

2. For concatenation, sprintf and snprintf behaves differently.

Due to some reasons, you would observe the following discrepancies in concatenation between sprintf and snprintf.

char buf[20] = “”;

sprintf(buf, “%s.%s”, buf, “this is “);

sprintf(buf, “%s.%s”, buf, “a concat”);

The resulting buffer will be “this is a concat”. You would expect a same semantic formation for the snprintf too as follows.

snprintf(buf, sizeof(buf), “%s.%s”, buf, “this is “);

snprintf(buf, sizeof(buf), “%s.%s”, buf, “a replacement”);

This time the resulting beffer will only contain “a replacement”. This is because the buffer is overwritten when we try concatenation in the above format. The below is the correct usage.

snprintf(buf, sizeof(buf), “%s”, “this is “);

snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), “%s”, ” correct”);

Now “this is correct”.

NOTE: You will find this useful in differentiating between ‘sizeof’ and ‘strlen’.

In both sprintf and snprintf, buffer is considered as “Pointer to a buffer where the resulting C-string is stored”. This means that you can use the address of an element of the buffer to append a character array. Thus ‘&buf[strlen(buf)]’ in place of ‘buf+strlen(buf)’ above will also give the same result.

NOTE: But don’t try ‘&buf+strlen(buf)’ or &(buf+strnlen(buf)’ as these would give you incorrect results or compilation error respectively.

 

3. strncat in succession

char buf[BUF_SIZE];

strncpy(buf, “some string”, sizeof(buf)-1);

buf[sizeof(buf)-1] = ‘\0’;

strncat(buf, “, another string”, sizeof(buf) – strlen(buf) – 1);

Again someone can argue that strncpy of “some string” would also copy the null terminator into the buffer (buf) and hence manual null terminating is meaningless. But what if the string length of “some string” is greater than BUF_SIZE?

Needless to say, we can straight away use the strncat without using strncpy at the beginning as follows.

buf[sizeof(buf)-1] = ‘\0’;

strncat(buf, “some string”, sizeof(buf) – strlen(buf) – 1);

strncat(buf, “, another string”, sizeof(buf) – strlen(buf) – 1);

 

4. strncat always null-terminated but strncpy doesn’t

char dest[64];

for strncat, dest should have a ‘\0’ at somewhere in the array. dest[0] = ‘\0’; would be enough.

for strncpy,

dest[min(strlen(src), sizeof(dest)-1)] = ‘\0’;

is needed due to the following reasons.
i. to accomodate the src string which is sized equal or greater than 63 where its ‘\0’ is not copied when strncpy is performed.

dest[sizeof(dest)-1] = ‘\0’;

ii. if you perform a substring copy like the following,

strncpy(dest, src, 5);

and if the string length of src is greater than or equal to 5 characters (given that sizeof(dest) > 5), then the copy will not include ‘\0’ in its copy. For that, you will have to manually add the null character as follows.

dest[5] = ‘\0’;

 

What if, “strlen(src) < 5 < sizeof(dest)”?

The following example is directly taken from here for an illustration.

#include <iostream>
#include <cstring>

int main()
{
const char* src = “hi”;
char dest[6] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’};
std::strncpy(dest, src, 5);

std::cout << “The contents of dest are: “;
for (char c : dest) {
if (c) {
std::cout << c << ‘ ‘;
} else {
std::cout << “\\0” << ‘ ‘;
}
}
std::cout << ‘\n’;
std::cout << dest << std::endl;
}

The resulting dest will be: h i \0 \0 \0 f

This shows that the copy will only ensure the ‘\0’ is added only upto the size specified after copying the src.

You can do the following for a defined string.

char* c = “hi”;

instead of,

strncpy(buf, c, 2);

buf[2] = ‘\0’;

The below would be sufficient as this will copy ‘\0’ of “hi” as well.

strncpy(buf, c, 3);

 

5. strncat as a better alternative to strncpy.

strncat and strncpy need direct or indirect way of making room for the null character at the end. This means that the above point 1. is not applicable for strncat and strncpy where you cannot use the full length of a buffer. And it is necessary to use ‘size-1’ format when you specify the full length.

strncpy(dest, src, sizeof(dest)-1);

Someone would suggest you to do the following in case of strncpy.

char dest[100];

strncpy(dest, src, sizeof(dest));

dest[sizeof(dest)-1] = ‘\0’;

One would argue that if there is a defined character array in ‘src’, it should contain the ‘\0’ and adding null character at the end is not necessary. What if the length of ‘src’ is higher than equal to the maximum length of ‘dest’? The ‘\0’ will be beyond the sizeof(dest) and something innocent like ‘printf(“%s\n”, dest);’ would also crash.

But strncat comes as a handy alternative to strncpy as follows.

*dest = ‘\0’;

strncat(dest, src, sizeof(dest)-1);

 

Please feel free to comment below, if you found anything unclear, wrong or buggy. Have a nice day!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s