首页 > 解决方案 > gcc-11 不正确可能会在未初始化的情况下使用,这似乎很难避免

问题描述

我发现了一个特定的使用模式,它看起来完全没问题,以前没有编译器抱怨过。现在它使用 gcc-11 发出警告:下面是一个接近最小的示例。另存为t.c并使用gcc-11 -O2 -Wall -c t.c.

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

编译结果:

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];
      |        ^~~

现在有一些观察

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

编辑由于我无法在评论中添加代码,因此这里是反驳部分声明的代码。这编译得很好:

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

标签: cgccgcc11

解决方案


编译器是正确的,虽然英文措辞不完善。

假设len总是积极的,解决方法是插入if (len <= 0) __builtin_unreachable();函数。这告诉编译器len始终为正,这意味着必须将一些数据写入调用s之前指向的内存点。f

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

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

char tmp[500];
f(tmp);

然后编译器抱怨“警告:'tmp'可能未初始化使用。” 很明显tmp,传递给函数的 不是未初始化的;它将是数组的地址。所以编译器必须警告它是可能未初始化使用的数组的内容。

请注意,虽然从for(o=s,i=0; i < len; i++)表面上开始的循环似乎初始化了指向的数据,但如果为零s,它不会。len并且由于s被传递给fwithout lenf因此无法知道其中没有任何内容s(通过某些侧通道(例如使用外部对象)除外)。所以大概在每次调用f中至少读取一些数据。s

这是一个较小的示例:

#include <stdlib.h>

extern void f(const char *s);

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

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

if (len <= 0) __builtin_unreachable();

这导致没有新的代码生成,但警告消失了。(实际上,生成的代码变小了,部分原因是编译器无需先测试就可以进入for循环i < len。)


推荐阅读