首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >gcc-11错误可能是未经初始化的,似乎很难避免。

gcc-11错误可能是未经初始化的,似乎很难避免。
EN

Stack Overflow用户
提问于 2021-11-01 19:59:39
回答 2查看 687关注 0票数 4

我发现了一种特殊的使用模式,它看上去完全没有问题,而且从来没有编译器抱怨过。现在,它对gcc-11提出了警告:下面是一个接近最小的例子。保存为t.c并使用gcc-11 -O2 -Wall -c t.c编译。

代码语言:javascript
复制
#include <stdlib.h>
#include <string.h>

extern void f(const char *s);

void
t(int len, char *d)
{ char tmp[500];
  char *s, *o;
  int i;

  if ( len <= sizeof(tmp) )
    s = tmp;
  else if ( !(s=malloc(len)) )
    return;

  for(o=s,i=0; i < len; i++)
    *o++ = d[i]+1;

  f(s);
//  i = strlen(s);
  if ( s != tmp )
    free(s);
}

汇编的结果如下:

代码语言:javascript
复制
gcc-11 -O2 -Wall -c t.c
t.c: In function ‘t’:
t.c:20:3: warning: ‘s’ may be used uninitialized [-Wmaybe-uninitialized]
   20 |   f(s);
      |   ^~~~
t.c:4:13: note: by argument 1 of type ‘const char *’ to ‘f’ declared here
    4 | extern void f(const char *s);
      |             ^
t.c:20:3: warning: ‘tmp’ may be used uninitialized [-Wmaybe-uninitialized]
   20 |   f(s);
      |   ^~~~
t.c:4:13: note: by argument 1 of type ‘const char *’ to ‘f’ declared here
    4 | extern void f(const char *s);
      |             ^
t.c:8:8: note: ‘tmp’ declared here
    8 | { char tmp[500];
      |        ^~~

现在有一些观察

  • 调用strlen(s)而不是f(s)会导致警告而不是。请注意,两者都接受/usr/include/string.h)
  • If (在either.
  • Initializing中选中I删除了f()声明中的const ),问题也消失了。使用f((const char*)s)进行
  • 调用也无助于either.
  • Initializing s,因为在char *s = NULL中也没有帮助(而且我也不想这样做,因为它隐藏了关于不初始化变量的正确警告)。

我了解到,声称某事是GCC的错误通常是正确的证明是错误的。所以我先在这里检查我错过了什么。

编辑,因为我不能将代码放在注释中,下面是否定部分声明的代码。这可以很好地编译:

代码语言:javascript
复制
extern void f(const char *__s);

void
t(int len, char *d)
{ char tmp[500];
  char *s=tmp, *o;
  int i;

  for(o=s,i=0; i < len; i++)
    *o++ = d[i]+1;

  f(tmp);
}
EN

回答 2

Stack Overflow用户

发布于 2021-11-01 20:14:37

尽管英语短语不完美,但编译器是正确的。

假设len总是正的,那么修复方法就是在函数中插入if (len <= 0) __builtin_unreachable();。这告诉编译器len总是正数,这意味着在调用f之前,必须将一些数据写到s指向的内存中。

当编译器说“‘s’‘可以不初始化地使用’s‘时,这并不意味着可以使用s的值,而是意味着可以使用它所指向的值,并且没有初始化指向内存的值。请注意,s被传递给接受const char *s的函数,这意味着该函数不会修改s指向的数据,因此希望它已经包含数据。C标准并不严格要求这一点;只要没有用const定义指向内存的指针,f就允许将指针转换为原始的char *并在那里修改数据,但是参数声明的含义是它不会。

我们可以通过将函数的主体更改为:

代码语言:javascript
复制
char tmp[500];
f(tmp);

然后编译器会发出“警告:'tmp‘可能未初始化”。显然,传递给函数的tmp不是未初始化的;它将是数组的地址。因此,编译器必须警告,可能未初始化的是数组的内容。

注意,虽然以for(o=s,i=0; i < len; i++)开头的循环表面上似乎初始化了s指向的数据,但如果len为零,则不会。而且,由于s是在没有len的情况下传递给f的,所以f无法知道s中的任何东西(除了使用外部对象之类的侧通道)。因此,据推测,f在每次调用中至少读取s中的一些数据。

下面是一个较小的例子:

代码语言:javascript
复制
#include <stdlib.h>

extern void f(const char *s);

void t(int len)
{
    char *s = malloc(len);
    f(s);
    free(s);
}

想必,len总是积极的。要告诉GCC这一点,请在函数中插入这一行:

代码语言:javascript
复制
if (len <= 0) __builtin_unreachable();

这样就不会生成新的代码,但是警告就会消失。(实际上,生成的代码越来越小,部分原因是编译器可以在不首先测试for的情况下进入i < len循环。)

票数 4
EN

Stack Overflow用户

发布于 2021-11-01 20:38:38

gcc-11的发行说明对此作了解释。

https://www.gnu.org/software/gcc/gcc-11/changes.html

也许-未初始化现在包括了默认值。

https://gcc.gnu.org/onlinedocs/gcc-11.1.0/gcc/Warning-Options.html#index-Wmaybe-uninitialized

如果无法证明未初始化的路径在运行时未执行,

编译器将发出警告。

即使对作者来说很清楚,编译器也有责任证明已经初始化了数据,否则就会设置警告。

-Wmaybe-uninitialized-Wall触发

真正的问题是--为什么不直接初始化数据呢?

这是示例代码,但它看起来像是等待漏洞的漏洞,因为tmp可能是任意的非空内存,因此len可能存在,因为没有空终止符(或嵌入的空值可能存在)的保证,但是len没有传递到f

代码语言:javascript
复制
#include <stdlib.h>
#include <string.h>

extern void f(const char *s);

void
t(int len, char *d)
{ char tmp[500] = {0};  //ensure nulls
  char *s = tmp;
  char *o = s;
  int i;

  if ( len <= sizeof(tmp) )
    s = tmp;
  else if ( !(s=malloc(len)) )
    return;
  memset(s+len, 0, 1); // ensure a trailing null
  for(i=0; i < len-1; i++) // leave that null there at least
    *o++ = d[i]+1;

  f(s);
//  i = strlen(s);
  if ( s != tmp )
    free(s);
}

gcc编译它时没有警告。

代码语言:javascript
复制
gcc -O2 -Wall -c t.c
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69802195

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档