【问题标题】:gcc-8 -Wstringop-truncation what is the good practice?gcc-8 -Wstringop-truncation 有什么好的做法?
【发布时间】:2018-10-16 07:43:55
【问题描述】:

GCC 8 添加了-Wstringop-truncation 警告。来自https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82944

在 GCC 8.0 中通过 r254630 为错误 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]name 是一个长度可能大于32 的char*。我想将name 复制到this->name 中,如果大于32 则截断它. size_t 应该是 31 而不是 32?我糊涂了。 this->name 不是必须以 NUL 结尾的。

【问题讨论】:

  • 如果name"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",你想让this->name 持有"ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"(一个字符串)还是{'A','B',...,'e','f'}(一个字符序列,没有空字符来终止它) ?
  • 一个字符序列,没有空字符来终止它。

标签: c++ gcc strncpy gcc8


【解决方案1】:

此消息试图警告您,您正在做的正是您正在做的事情。很多时候,这不是程序员的本意。如果这是您想要的(意思是,您的代码将正确处理字符数组最终不会包含任何空字符的情况),请关闭警告。

如果您不想或无法在全局范围内关闭它,您可以按照@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
}

【讨论】:

  • 谢谢。我的问题是我将这段代码发布在一个我对编译选项几乎没有控制权的地方(在 CRAN 上,保存 R 编程语言包的地方)。我可以对memcpy 执行相同操作以跳过警告而不更改编译选项吗?
  • 您可以使用#pragma 在本地关闭警告。
  • 我的代码必须在没有警告的情况下编译,至少使用 gcc 5,6,7,8 和 clang 3,4。此编译指示语句引发警告。有没有专门针对 gcc 8 的方法?
  • @JRR 如果需要,可以检查__GNUC__,但我建议尽可能检查行为而不是检查版本。您可以改为忽略 -Wpragmas-Wunknown-warning-option-Wstringop-truncation(按此顺序)。 GCC 4.6 及更高版本以及 clang 3.5 及更高版本没有警告。
  • 这太冗长了,会导致其他编译器出现警告。查看我的解决方案stackoverflow.com/a/56402365/33708
【解决方案2】:

这个新的 GCC 警告使 strncpy() 在许多项目中几乎无法使用:代码审查将不接受会产生警告的代码。但是,如果strncpy() 仅用于足够短的字符串,以便它可以写入终止零字节,那么在开始时将目标缓冲区清零,然后使用纯 strcpy() 将实现相同的工作。

其实strncpy()是其中一个函数,最好不要放到C库中。当然,它有合法的用例。但是库设计者也忘记将strncpy() 的固定大小的字符串感知对应物放入标准中。最重要的此类函数 strnlen()strndup() 仅在 2008 年被包含在 POSIX.1 中,在 strncpy() 创建几十年后!并且仍然没有将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 参数,则不清楚是输出缓冲区的大小还是缓冲区的最大长度输入字符串(通常少一个)。

【讨论】:

    【解决方案3】:

    使用strncpy 的合理案例很少。这是一个相当危险的功能。如果源字符串长度(不含空字符)等于目标缓冲区大小,则strncpy 不会在目标缓冲区末尾添加空字符。所以目标缓冲区不会被空终止。

    我们应该在 Linux 上写这样的代码:

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

    在您的情况下,如果您想在复制时截断源,但仍需要一个空终止的目标缓冲区,那么您可以编写这种代码:

    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);
    

    【讨论】:

    • 它还有一个很好的用途:当您需要将字符串复制到固定大小的缓冲区并将所有“未使用”字节设置为零时。这有时会出现,但重点是 strncpy() 不会产生 C 风格的以空字符结尾的字符串——它会做一些不同的事情。
    • 我不确定你是否理解 strncopy 的不合理使用。我稍微编辑了我的问题以解释我的意图。基本上我希望它将源截断为 32 字符到目标。
    • @JRR 你希望destination 仍然是 NUL 终止的吗?因为在这种情况下,strncpy 是错误的函数。
    • @JRR 我编辑了我的答案,但你应该编辑你的问题,这不清楚。
    • -1+1的作用是什么?我猜它是用来排除 nul 字节
    【解决方案4】:

    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。我在代码中添加了这样的处理,警告消失了。

    【讨论】:

      【解决方案5】:

      其他人的回答让我只写了一个简单的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 为空终止。我正在使用 g++ 编译器并希望避免杂注条目。

      【讨论】:

        【解决方案6】:

        我发现抑制警告的最佳方法是将表达式放在括号中 like this gRPC patch:

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

        #pragma 诊断抑制解决方案的问题在于,当编译器无法识别 pragma 或特定警告时,#pragma 本身会导致警告;也太冗长了。

        【讨论】:

        • 不适用于 strncat。就个人而言,我不喜欢这种带括号的魔法。
        【解决方案7】:

        它说的是我们只能使用 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';
        

        【讨论】:

          【解决方案8】:

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

          #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")
          

          查看全文here

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-06-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-12-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多