gcc-8 -Wstringop-truncation什么是好的做法?

oxf4rvwz  于 2023-05-18  发布在  Go
关注(0)|答案(9)|浏览(323)

GCC 8添加了-Wstringop-truncation警告。https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82944
GCC 8.0中通过r254630为bug 81117添加的-Wstringop-truncation警告专门用于强调strncpy函数可能的意外使用,该函数从源字符串中截断终止NUL字符。请求中列举的滥用示例如下:

char buf[2];

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

我得到了同样的警告与此代码。

strncpy(this->name, name, 32);

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

考虑到this->namechar name[32]namechar*,其长度可能大于32。我想将name复制到this->name中,如果它大于32,则将其截断。size_t应该是31而不是32吗?我很困惑this->name不是必须以NULL终止的。

2uluyalo

2uluyalo1#

这条信息是在警告你你正在做的事情。很多时候,这不是程序员的意图。如果这是您想要的(这意味着,您的代码将正确处理字符数组最终不包含任何空字符的情况),请关闭警告。
如果你不想或不能全局关闭它,你可以在本地关闭它,@doron指出:

#include <string.h>
char d[32];
void f(const char *s) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
    strncpy(d, s, 32);
#pragma GCC diagnostic pop
}
6mzjoqzu

6mzjoqzu2#

这个新的GCC警告使得strncpy()在许多项目中几乎不可用:代码审查将不接受代码,这会产生警告。但是如果strncpy()只用于足够短的字符串,以便它可以写入终止零字节,那么在开始时将目标缓冲区置零,然后纯strcpy()将实现相同的工作。
实际上,strncpy()是其中一个函数,他们最好不要放在C库中。有合法的使用案例,当然。但是库设计者忘记了将固定大小的字符串识别对应的strncpy()也加入到标准中。最重要的函数strnlen()strndup(),在strncpy()创建几十年后才被纳入POSIX. 1中!并且仍然没有函数,将strncpy()生成的固定长度字符串复制到具有正确C语义的预分配缓冲区中,即始终写入0终止字节。一个这样的函数可以是:

// 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;
}

我建议对strncpy_t()使用两个长度输入,以避免混淆:如果只有一个size参数,则不清楚它是输出缓冲区的大小还是输入字符串的最大长度(通常少一个)。

osh3o9ms

osh3o9ms3#

使用strncpy的理由很少。这是一个相当危险的功能。如果源字符串长度(不含空字符)等于目标缓冲区的大小,则strncpy不会在目标缓冲区的末尾添加空字符。因此目标缓冲区不会以空终止。
我们应该在Linux上写这样的代码:

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

在你的例子中,如果你想在拷贝时截断源,但仍然想要一个null终止的目标缓冲区,那么你可以写这样的代码:

destSize = 32

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

编辑:哦...如果这个函数不强制以NULL结尾,那么strncpy是正确的函数。是的,你需要用32而不是31来调用它。我认为你应该忽略这个警告,禁用它...老实说,我没有一个好的答案...
Edit2:为了模拟strncpy函数,可以编写以下代码:

destSize = 32

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

vxbzzdmp4#

TL;DR:处理截断情况,警告消失。

这个警告对我来说非常有用,因为它发现了我代码中的一个问题。考虑以下列表:

#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
正如注解所说,short_string[7] = '\0';在这里是必要的。关于strncpy
警告:如果src的前n个字节中没有空字节,则放置在dest中的字符串将不会以空结尾。
如果我们删除这一行,它将调用UB。例如,对于我来说,程序开始打印:
short_string =“这是一个很长的字符串”
基本上,GCC希望您修复UB。我在代码中添加了这样的处理,警告消失了。

cygmwpex

cygmwpex5#

其他人的回复让我只写了一个简单版本的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;
     }

它避免了警告并保证dest是null终止的。我使用的是g++编译器,希望避免杂注条目。

jfgube3f

jfgube3f6#

我发现抑制警告的最好方法是将表达式放在括号中,就像下面这个gRPC补丁一样:

(strncpy(req->initial_request.name, lb_service_name,
         GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH));

#pragma诊断抑制解决方案的问题是,当编译器无法识别杂注或特定警告时,#杂注本身将导致警告;而且它太冗长了。

r9f1avp5

r9f1avp57#

我在寻找这个问题的近乎完美的解决方案时发现了这一点。由于这里的大多数答案描述了在不抑制警告的情况下如何处理的可能性和方法。公认的答案建议使用以下 Package ,这会导致另一组警告,令人沮丧,不可取。

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
    ...
#pragma GCC diagnostic pop

相反,我发现这个工作的解决方案,不能说如果有任何陷阱,但它做的工作很好。

_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"")
    strncpy(d, s, 32);
_Pragma("GCC diagnostic pop")

在这里看到完整的文章。

db2dz4w8

db2dz4w88#

它说的是,我们只能使用len - 1字符,因为最后一个应该是'\0',所以use似乎清除了警告,我们只能复制len - 1...
通过示例:

strncpy(this->name, name, 31);

#include <string.h>
char d[32];
void f(const char *s) {
    strncpy(d, s, 31);
}
d[31] = '\0';
zpqajqem

zpqajqem9#

只要禁用杂注警告即可。然后,警告禁用本地工作。

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wstringop-truncation"
    ...
#pragma GCC diagnostic pop

相关问题