0

I just realized that I am in the habit of using snprintf for taking slices of strings, which now seems unconventional to me for several reasons (chiefly, the need to #include <stdio.h>). But after looking at strncpy more closely, I'm struggling to see why anyone would prefer it over snprintf.

To be clear, I'm comparing strncpy(dest, src, count) with snprintf(dest, count + 1, "%s", src). snprintf(dest, count + 1, src) obviously causes problems whenever src contains '%'.

What stood out to me most was how much more snprintf acts like an str- function than strncpy, which by comparison feels more like a mem- function to me.

  • If strncpy reaches the end of src before it copies count characters, it will write count - strlen(src) null characters in addition to the terminating null character copied from src.
    • This practice seems unnecessarily wasteful: functions expecting null-terminated strings (virtually every string function in C, for better or worse) would encounter the terminating null character and halt. Only mem- functions would even notice the additional characters, but I can't imagine the difference would ever be particularly useful. Also, if you're actually dealing with byte arrays, why are you using string functions?
    • From what I can tell, this is by-and-large not how C tends to deal with either arrays or strings. For example, char foo[6] = "hi"; doesn't fill the remaining 3 bytes with null characters! Furthermore, in my experience, arrays are typically pre-initialized in this manner using... you guessed it, memset. A mem- function.
  • If strncpy copies count characters before it reaches the end of src, the resulting character array won't be null-terminated. That's right, the string copying function sometimes doesn't produce a valid string. What?
  • strncpy's count argument doesn't include the terminating null character. This makes memory allocation a bit awkward since the character array must be allocated with at least count + 1 bytes in order to store count characters as a proper null-terminated string. Even worse, the last character in the array still needs to be set to the null character, which you can only do with strncpy if you know that strlen(src) == count + 1.

Compare all of this to snprintf:

  • snprintf copies at most count - 1 characters and a null character, meaning you need an array of count elements to store it all. Sweet!
  • If snprintf reaches the end of src before it copies count - 1 characters, it just adds a terminating null character to dest and halts - no questions asked.
  • If snprintf copies count - 1 characters before it reaches the end of src, it still adds a terminating null character to the end of dest.

Why on earth is strncpy this way?

QuaternionsRock
  • 832
  • 7
  • 14
  • 4
    `strncpy` is probably faster (`sprintf` has to interpret the format string) and some implementations do no support the `printf` function family. – Weather Vane Jul 15 '21 at 07:46
  • 2
    Read this: https://stackoverflow.com/questions/1453876/why-does-strncpy-not-null-terminate. Basically `strncpy` is more or less useless. – Jabberwocky Jul 15 '21 at 07:54
  • 2
    If you are using the `n` part to guard agaisnt buffer overflow, then the code is broken anyway: you'll truncate necessary data. Also `sprintf` is guaranteed to NUL-terminate the output array while `strncpy` is not. – Weather Vane Jul 15 '21 at 07:55
  • Nothing wrong with either one, though the `sprintf/snprintf` functions were intended to handle a variadic number of arguments providing data for the conversions within your format-string. If you have no need for conversions and are simply writing a text string, then `strcpy/strncpy` are the tools for the job. – David C. Rankin Jul 15 '21 at 08:27
  • On systems with limited resources you'll prefer `strncpy()` because it is so much smaller than `snprintf()`. – the busybee Jul 15 '21 at 08:42
  • 1
    "For example, char foo[6] = "hi"; doesn't fill the remaining 3 bytes..." That is wrong. They will be initialized. – Support Ukraine Jul 15 '21 at 10:13
  • "strncpy's count argument doesn't include the terminating null character" It does. As the standard says: "The strncpy function copies not more than n characters (characters **that follow** a null character are not copied) ... " So the null character is included. – Support Ukraine Jul 15 '21 at 10:39
  • @4386427 *It does.* No, it does not. [Footnote 308](https://port70.net/~nsz/c/c11/n1570.html#note308): "Thus, if there is no null character in the first n characters of the array pointed to by s2, the result will not be null-terminated." Not the use of terms such as `n-1` for [functions such as `snprintf()`](https://port70.net/~nsz/c/c11/n1570.html#7.21.6.5p2): "... output characters beyond the n-1st are discarded..." That all-important `n-1`-type term is lacking in `strncpy()` - it does not *include* any terminator, it *pads* the target buffer with zero bytes only if necessary. – Andrew Henle Jul 15 '21 at 11:09
  • @AndrewHenle Different thing. It still copy `count` characters. That's the whole point of the function. Always write `count` characters to the destination – Support Ukraine Jul 15 '21 at 11:12
  • 1
    @Jabberwocky IMO it's worse than useless. It's pernicious. It's name belies its misleading semantics. It's name implies it copies the more-or-less standard "at most `n-1` bytes" into the target buffer then "add a null terminator", to use the C standard's words. But that's not what it does... – Andrew Henle Jul 15 '21 at 11:12

2 Answers2

3

What is the advantage of using strncpy over snprintf?

Well, it doesn't really make sense to compare two functions that are really intended to do different things.

strncpy is designed to write exactly n characters to dst. Some or all (but at least 1) of these n characters may be copied from the string src and the remaining characters (if any) will be null characters. Unfortunately many C programmers think that strncpy is a "safe" version of strcpy but it's not and it's not what was intended. The name strncpy is probably what is misleading.

snprintf is designed for a completely different task, i.e. formatted printing to a string.

Special use cases of sprintf like sprintf(dst, n, "%s", src) may seem equivalent to strncpy(dst, src, n) because the result dst when viewed as a "traditional C style" string looks the same. But it's not truely identical calls. They do different things to generate the result dst and dst may be different when looking at all array elements (see example in the end of this answer).

Why on earth is strncpy this way?

strncpy is handy when working with strings stored in fixed size arrays where no termination character is used and null padding is used for strings shorter than the array size.

Example of snprintf appearing to do the same as strncpy while it's not

int main ()
{
    char src[] = "Hi";
    char dst[6];
    strcpy(dst, "Hello");
    puts(dst);
    strncpy(dst, src, 6);
    puts(dst);
    puts("------------------");
    strcpy(dst, "Hello");
    puts(dst);
    snprintf(dst, 6, "%s", src);
    puts(dst);    
    return 0;
}

Output

Hello
Hi
------------------
Hello
Hi

From the output it seems that the strncpy call and the snprintf call did the same, i.e. changed dst from "Hello" to "Hi".

However, a closer look at dst - like

void pp(const char* p)
{
    for (int i=0; i < 6; ++i) printf("%02X ", p[i]);
    puts("");
}

int main ()
{
    char src[] = "Hi";
    char dst[6];
    strcpy(dst, "Hello");
    puts(dst);
    strncpy(dst, src, 6);
    puts(dst);
    pp(dst);
    puts("------------------");
    strcpy(dst, "Hello");
    puts(dst);
    snprintf(dst, 6, "%s", src);
    puts(dst);    
    pp(dst);
    return 0;
}

Output

Hello
Hi
48 69 00 00 00 00 
------------------
Hello
Hi
48 69 00 6C 6F 00 

shows that the two calls actually resulted in different dst values.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • Your example at the end still seems superficial to me. The definition of a "string" in C is *strictly* a null-terminated sequence of `char`s. Under this pretense, `48 69 00 00 00 00` and `48 69 00 6C 6F 00` should be considered equal (as is demonstrated by `strcmp`). The only time they should be considered distinct is with working with `void` arrays, a task that is restricted to `mem-` functions. `strncpy` therefore performs unnecessary operations for working with strings that technically impede performance (although not meaningfully in most cases). – QuaternionsRock Jul 25 '21 at 17:01
  • @QuaternionsRock The whole point is that `strncpy` wasn't design to generate the atandard C-style string. – Support Ukraine Jul 26 '21 at 06:15
2

What is the advantage of using strncpy over snprintf?

Virtually none. The only advantage of strncpy (over anything) is if you want to create what the C FAQ list calls a "now-obsolete data structure, the fixed-length, not-necessarily-\0-terminated 'string.'"

If your goal is to copy the string src to the destination dst, as a proper (but possibly truncated) string, and without overflowing dst's size dstsize, then

snprintf(dst, dstsize, "%s", src);

is actually a pretty good way to do it. (Not the only way, and not a bad way, but perhaps overkill.)

On the other hand, strncpy(dst, src, dstsize), is no solution to this problem at all.

Other possible solutions to this problem are

*dst = '\0'; strncat(dst, src, dstsize);

strlcpy(dst, src, dstsize);

strcpy_s(dst, dstsize, src);

Why on earth is strncpy this way?

In the early days of C and Unix, it was somewhat common to use short, fixed-length pseudo-strings which were not necessarily null-terminated, but which were padded out to their "full length" with extra \0's if necessary. This meant that you could efficiently compare them using the equivalent of memcmp; you didn't have to check for \0 at all. See also the footnote to the aforementioned C FAQ list entry.

strncpy is pretty much obsolete today; many programmers recommend avoiding it entirely.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103