我发现了一种特殊的使用模式,它看上去完全没有问题,而且从来没有编译器抱怨过。现在,它对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];
| ^~~现在有一些观察
/usr/include/string.h)f()声明中的const ),问题也消失了。使用f((const char*)s)进行char *s = NULL中也没有帮助(而且我也不想这样做,因为它隐藏了关于不初始化变量的正确警告)。我了解到,声称某事是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);
}发布于 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 *并在那里修改数据,但是参数声明的含义是它不会。
我们可以通过将函数的主体更改为:
char tmp[500];
f(tmp);然后编译器会发出“警告:'tmp‘可能未初始化”。显然,传递给函数的tmp不是未初始化的;它将是数组的地址。因此,编译器必须警告,可能未初始化的是数组的内容。
注意,虽然以for(o=s,i=0; i < len; i++)开头的循环表面上似乎初始化了s指向的数据,但如果len为零,则不会。而且,由于s是在没有len的情况下传递给f的,所以f无法知道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循环。)
发布于 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。
#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编译它时没有警告。
gcc -O2 -Wall -c t.chttps://stackoverflow.com/questions/69802195
复制相似问题