gcc-8 -Wstringop-truncation what is the good practice?

27,921

Solution 1

This new GCC warning renders strncpy() mostly unusable in many projects: Code review will not accept code, that produces warnings. But if strncpy() is used only with strings short enough, so that it can write the terminating zero byte, then zeroing out the destination buffer in the beginning and then plain strcpy() would achieve the same job.

Actually, strncpy() is one of the functions, that they had better not put into the C library. There are legitimate use cases for it, sure. But library designers forgot to put fixed size string aware counterparts to strncpy() into the standard, too. The most important such functions, strnlen() and strndup(), were only included 2008 into POSIX.1, decades after strncpy() was created! And there is still no function, that copies a strncpy() generated fixed-length string into a preallocated buffer with correct C semantics, i.e. always writing the 0-termination byte. One such function could be:

// Copy string "in" with at most "insz" chars to buffer "out", which
// is "outsz" bytes long. The output is always 0-terminated. Unlike
// strncpy(), strncpy_t() does not zero fill remaining space in the
// output buffer:
char* strncpy_t(char* out, size_t outsz, const char* in, size_t insz){
    assert(outsz > 0);
    while(--outsz > 0 && insz > 0 && *in) { *out++ = *in++; insz--; }
    *out = 0;
    return out;
}

I recommend to use two length inputs for strncpy_t(), to avoid confusion: If there was only a single size argument, it would be unclear, if it is the size of the output buffer or the maximum length of the input string (which is usually one less).

Solution 2

There are very little justified case for using strncpy. This is a quite dangerous function. If the source string length (without the null character) is equal to the destination buffer size, then strncpy will not add the null character at the end of the destination buffer. So the destination buffer will not be null terminated.

We should write this kind of code on Linux:

lenSrc = strnlen(pSrc, destSize)
if (lenSrc < destSize)
    memcpy(pDest, pSrc, lenSrc + 1);
else {
    /* Handle error... */
}

In your case, if you want to truncate the source on copy, but still want a null terminated destination buffer, then you could write this kind of code:

destSize = 32

sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp);
pDest[sizeCp] = '\0';

Edit: Oh... If this not mandatory to be NULL terminated, strncpy is the right function to use. And yes you need to call it with 32 and not 31. I think you need to ignore this warning by disabling it... Honestly I do not have a good answer for that...

Edit2: In order to mimic the strncpy function, you could write this code:

destSize = 32

sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp + 1);

Solution 3

TL;DR: handle the truncation case and the warning will dissappear.


This warning happened to be really useful for me, as it uncovered an issue in my code. Consider this listing:

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

int main() {
    const char long_string[] = "It is a very long string";
    char short_string[8];
    
    strncpy(short_string, long_string, sizeof(short_string));

    /* This line is extremely important, it handles string truncation */
    short_string[7] = '\0';

    printf("short_string = \"%s\"\n", short_string);

    return 0;
}

demo

As the comment says short_string[7] = '\0'; is necessary here. From the strncpy man:

Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated.

If we remove this line, it invokes UB. For example, for me, the program starts printing:

short_string = "It is a It is a very long string"

Basically, GCC wants you to fix the UB. I added such handling to my code and the warning is gone.

Solution 4

The responses from others led me to just write a simple version of strncpy.

    #include<string.h>

    char* mystrncpy(char* dest, const char*src, size_t n) {
        memset(dest, 0, n);
        memcpy(dest, src, strnlen(src, n-1));
        return dest;
     }

It avoids the warnings and guarantees dest is null terminated. I'm using the g++ compiler and wanted to avoid pragma entries.

Share:
27,921
JRR
Author by

JRR

Updated on October 02, 2021

Comments

  • JRR
    JRR over 2 years

    GCC 8 added a -Wstringop-truncation warning. From https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82944 :

    The -Wstringop-truncation warning added in GCC 8.0 via r254630 for bug 81117 is specifically intended to highlight likely unintended uses of the strncpy function that truncate the terminating NUL charcter from the source string. An example of such a misuse given in the request is the following:

    char buf[2];
    
    void test (const char* str)
    {
      strncpy (buf, str, strlen (str));
    }
    

    I get the same warning with this code.

    strncpy(this->name, name, 32);
    
    warning: 'char* strncpy(char*, const char*, size_t)' specified bound 32 equals destination size [-Wstringop-truncation`]
    

    Considering that this->name is char name[32] and name is a char* with a length potentially greater than 32. I would like to copy name into this->name and truncate it if it is greater than 32. Should size_t be 31 instead of 32? I'm confused. It is not mandatory for this->name to be NUL-terminated.