Search code examples
cconcatenationnull-terminated

Does snprintf/sprintf overwrite the terminating-null of the penultimate argument, just like strcat does?


It is clearly written for strcat, e.g. here and here that, in case,

char *strcat(char *s1, const char *s2);

then,

The initial character of s2 overwrites the null character at the end of s1.

But apparently searching a little here for "concatenating strings/literals in C", I stumbled upon this, which states,

Avoid using strcat in C code. The cleanest and, most importantly, the safest way is to use snprintf:

So, is the same also true for snprintf/sprintf that the first character of the next argument overwrites the null-termination of the previous argument? I can see no such reference in the documentation.

Empirically evidence seems to suggest, that both strcat & snprintf act the same way. Or is my assumption wrong?

#include <string.h>
#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf( "Test Program Started\n");
    const char* first = "a";
    const char* second = "b";
    const char* third = "c";
    const int merged_length = (strlen(first) + strlen(second) + strlen(third) + 1); // +1 for null-termination

    char* catResult;
    catResult = malloc( merged_length * sizeof(char));
    strcpy(catResult, first);
    strcat(catResult, second);
    strcat(catResult, third);
    catResult[merged_length] = '\0';
    printf("catResult:%s \tstrlen(catResult):%d \t sizeof(catResult):%d\n", 
            catResult, 
            strlen(catResult), 
            sizeof(catResult));
    free(catResult);

    char* snprintfResult;
    snprintfResult = malloc( merged_length * sizeof(char));
    snprintf(snprintfResult, merged_length, "%s%s%s", first, second, third);
    // catResult[merged_length] = '\0'; // not necessary as per documentation
    printf("snprintfResult:%s \tstrlen(snprintfResult):%d \tsizeof(snprintfResult):%d\n", 
            snprintfResult, 
            strlen(snprintfResult), 
            sizeof(snprintfResult));
    free(snprintfResult);
} 

Test Program Started
catResult:abc strlen(catResult):3 sizeof(catResult):4
snprintfResult:abc strlen(snprintfResult):3 sizeof(snprintfResult):4


Solution

  • You cannot use sizeof like that. While sizeof("string") works as you expect, sizeof(string pointer) always returns the same value on any given platform (usually 4 or 8).

    const int merged_length = (sizeof(first) + sizeof(second) + sizeof(third) + 1);

    should be

    const int merged_length = (strlen(first) + strlen(second) + strlen(third) + 1);

    When you write %s to snprintf, it copies the string to the target string without any trailing null. When the null terminator at the end of the format string is reached, the output string is also null terminated.

    So, the actual answer to your question is no, because the null was never written after first, but the final effect is more like if we answered yes, because both those code fragments do the same thing.