strcpy versus strncpy and -Wstringop-truncation

I had originally envisioned this post to be another “Old C dog, new C tricks” article, but it really is more of a “how long has it done this? did I just never read the manual?” article.

In C, strcpy() is used to copy a C string (series of character bytes with a 0 byte marking the end of the string) to a new buffer. Some years ago, I wrote a series about the dangers of strcpy() and other functions that look for that 0 byte at the end. I later wrote a follow-up that discussed strcat().

char buffer[10];

strcpy (buffer, "Hello");

Which means something like this can crash your program:

char buffer[10];

strcpy (buffer, "This will cause a buffer overrun.);

strncpy(), on the other hand, takes a third parameter which is the maximum number of characters to copy. Once it reaches that number, it stops copying. If you are copying to a 20-byte buffer, setting this value to 20 will ensure you do not overwrite that 20 byte buffer.

char buffer[20];

strncpy (buffer, "Hello", sizeof(buffer));

But there’s a problem… While strncpy() will blindly copy the source to the destination and then add the terminating 0 byte (without any care or concern to how much room is available at the destination), strncpy() will only add the terminating 0 if the source is smaller than the maximum size value.

This part I was aware of. In my earlier articles, I suggested this as a workaround to ensure the destination is always 0 terminated, even if the source string is as large or larger than the destination buffer:

char buffer[10];

strncpy (buffer, "1234567890", sizeof(buffer)-1); // subtract one
buffer[sizeof(buffer)-1] = '\0'; // 0 terminate

Above, sizeof(buffer) is 10. The copy would copy up to 9 bytes, so it would copy over “123456789”. Then, in the buffer at position 9 (bytes 0 to bytes 9 is ten bytes) it would place the zero.

Problem solved.

But that’s not important to this post.

Instead, there is another behavioral difference I wanted to mention — one that you likely already know. I must have know this and just forgotten. Surely I knew it. It even explains this in the C resource I use when looking up parameters to various C functions:

strncpy() will copy the bytes of the source buffer, and then pad the destination with zeros up until the maximum size value.

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

#define BUFFER_SIZE 32

void HexDump (const char *label, void *dataPtr, size_t dataSize);

int main()
{
    char buffer1[BUFFER_SIZE] = { 0 };
    char buffer2[BUFFER_SIZE] = { 0 };

    memset (buffer1, 0xff, sizeof(buffer1));
    memset (buffer2, 0xff, sizeof(buffer1));
    
    HexDump ("buffer1", buffer1, sizeof(buffer1));
    HexDump ("buffer2", buffer1, sizeof(buffer1));

    strcpy (buffer1, "Hello");
    strncpy (buffer2, "Hello", sizeof(buffer2));

    HexDump ("buffer1", buffer1, sizeof(buffer1));
    HexDump ("buffer2", buffer2, sizeof(buffer2));
    
    return 0;
}

void HexDump (const char *label, void *dataPtr, size_t dataSize)
{
    printf ("--- %s ---\n", label);
    for (unsigned int idx=0; idx<dataSize; idx++)
    {
        printf ("%02x ", ((unsigned char*)dataPtr)[idx]);
        if (idx % 16 == 15) printf ("\n");
    }
    printf ("\n");
}

Above, this program makes a buffer and sets all the bytes in it to 0xff (just so we can see the results easier).

It then uses strcpy() to copy a string (which will copy up until the 0 byte at the end of the source string), and strncpy() to copy the string to a second butter, with the maximum size specified as the destination buffer size.

Here is the output:

--- buffer1 ---
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

--- buffer2 ---
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

--- buffer1 ---
48 65 6c 6c 6f 00 ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

--- buffer2 ---
48 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

The first “buffer1” and “buffer2” shows the buffers initiated with 0xff’s.

The second “buffer1” shows the result of strcpy() – the five characters of “Hello” copied over, with a 0 byte added.

The second “buffer2” shows the same string copied using strncpy() with the size set to 16, the size of the buffer. You can see it copied the five characters of “Hello” and then filled the rest (up to the size) with 0 bytes.

Was this always the case with strncpy(), or did this get enhanced in later versions of C? I see this is fully documented at places like cplusplus.com:

Copy characters from string

Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.

No null-character is implicitly appended at the end of destination if source is longer than num. Thus, in this case, destination shall not be considered a null terminated C string (reading it as such would overflow).

– cplusplus.com entry on strncpy()

Why bring this up now?

The only reason I bring this up today is because I saw a new compiler warning I had never seen before recently.

warning: ‘strncpy’ output truncated before terminating nul copying X bytes from a string of the same length [-Wstringop-truncation]

That was the first time I’d ever seen this warning. It is a useful warning, since it informs you that the destination string will NOT be 0 terminated:

char buffer[10];
strncpy (buffer, "1234567890", 10);

And that reminded me of that strncpy() behavior, and made me change how I was using it in my program.

I also saw this variation:

warning: 'strncpy' output truncated copying X bytes from a string of length Y [-Wstringop-truncation]

This warning popped up when I had a source buffer that was longer than the destination. That should be fine, since strncpy() is told how much to copy over, max. I was puzzled why this would even be a warning. I mean, you specifically put the number in there that says “copy up to X bytes.”

I find it odd that the first message (exactly length) lets you know you don’t have a 0 terminated destination buffer, but the second just says “hey, we couldn’t copy as much as you request.”

Anyway, I found it interesting.

Until next time…

2 thoughts on “strcpy versus strncpy and -Wstringop-truncation

  1. William Astle

    Seems like it has always padded the destination with NULs. After doing a bit of research on its history (read: 2 minutes on the Google) even the lack of guaranteed NUL termination in the destination actually makes some sense. brain asplode

    Reply
    1. Allen Huffman Post author

      I was unaware of modern replacements such as strcpy_s() (I think it is?) I encountered when working with newer Windows/Mac compilers. I have a TODO blog post about those. I ran into that when trying to move code into a modern compiler and it wouldn’t let me use strcpy() without warnings. I suppose that fixes this even further trying to make these “safe”.

      I should have made a note that doing something like…

      char buffer[10];

      strncpy (buffer, “hello”, 100);

      …likely would be bad, since it would “stop” at the NIL, then keep going padding zeros. “But why would someone do that…”

      Reply

Leave a Reply to Allen HuffmanCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.