为啥 GCC 开始警告获取 void 表达式的地址?

Posted

技术标签:

【中文标题】为啥 GCC 开始警告获取 void 表达式的地址?【英文标题】:Why has GCC started warning on taking the address of a void expression?为什么 GCC 开始警告获取 void 表达式的地址? 【发布时间】:2014-12-03 03:53:47 【问题描述】:

几个 GCC 版本之前,我可以做这样的整洁的事情:

$ objcopy -I binary -O elf64-x86-64 -B i386 foo.png foo.png.o

...在C中加上以下,作为SDL图像加载的例子:

extern void _binary_foo_png_start;
extern void _binary_foo_png_start;
SDL_Surface *image = IMG_Load_RW(SDL_RWFromMem(&_binary_foo_png_start, &_binary_foo_png_end));

然后我将foo.png.o与C文件中的目标文件链接在一起,得到一个包含foo.png的可执行文件。

这些天,我仍然可以这样做,但 GCC 警告我:

foo.c:57:19: warning: taking address of expression of type ‘void’
foo.c:57:44: warning: taking address of expression of type ‘void’

显然它仍然有效,据我所知,它确实做到了它应该做的事情。符号本身没有明确定义的类型,因此将它们声明为void 似乎很合适。我的意思是,当然,我可以给他们任何其他任意类型,它仍然可以像我想要他们的地址一样工作,但声明他们void似乎比只是编造一些类型。

那么为什么 GCC 突然决定开始警告我呢?是否有其他首选方式可以做到这一点?

【问题讨论】:

也许,但是让我的输出冲击充满这些会压制有意义的警告。 :) 当然,更不用说它表明 GCC 建议不要这样做,我想知道为什么会建议这样做。 这是大自然告诉您避免使用非标准 GCC 结构的方式。你想要一些字节的地址,所以给它们char 类型并不完全是任意的。 如果您只对实际编程感兴趣,请将您的符号声明为char[],这是有效、直观且无警告的,然后继续。如果你想知道你违反了标准的哪些部分,这里是另一个:“每个声明符声明一个标识符,并断言当一个与声明符形式相同的操作数出现在表达式中时,它指定一个函数或对象声明说明符指示的范围、存储期限和类型”。没有 void 类型的对象,所以你不应该声明一个。 不能用extern char[] _binary_foo_png等吗? 【参考方案1】:

看来至少 C11 标准不允许这样做:

6.3.2.1/1 左值是一个表达式(对象类型不是 void),它可能 指定一个对象。

如果你的表达式不是左值,你不能取它的地址。

声明的有效性

extern void _binary_foo_png_start;

是有问题的,因为它可以说没有声明对象(对象不能具有void 类型)。我试过的四个 C 编译器中有两个接受它。这些编译器之一接受&_binary_foo_png_start。提交了一个错误。

从历史上看,它似乎曾经是允许此类构造的意图(这可能解释了为什么 Gcc 过去接受它)一些类似的讨论可以在 DR 12 中找到。请记住,lvalue 等相关定义在 C90、C99 和 C11 中有所不同。

【讨论】:

对于声明 extern void _binary_foo_png_start; 本身,我不确定它是否只是未定义的行为(因为无法定义)或者是否需要诊断(我找不到涵盖此的约束) . 回想起来,我什至注意到,您(非常有趣的)与 DR 12 的链接建议使用 extern const void 而不是简单的 extern void,GCC 实际上并没有发出警告。跨度> @Dolda2000 -- extern const void 与 gcc 4.9.3 (gcc-arm-none-eabi-4_9-2015q3) 完美配合,而 extern void 会产生警告。 @Dmitry:谢谢你的提示!这正是我所需要的。【参考方案2】:

C99 规范保证 char 具有 sizeof(char) == 1 (6.5.3.4),它还声明“sizeof 运算符产生其操作数的大小(以字节为单位)” - 因此 char 可以用作表示字节。

鉴于 PNG 图像也以字节为单位,因此您应该使用char(或相关:char* 或数组)来表示以字节为单位的任意二进制数据(例如 PNG 图像) .

因此,我会将您的 void _binary_foo_png_start; 更改为 char _binary_foo_png_start 并可能将 typedef char byte; 语句添加到共享头文件中。

稍微详细一点:“字节”是内存中最小的直接可寻址单元,一个字节不保证是 8 位(一个八位字节),它可能更大 - 但是如果一个字节超过 8 位,它可以预期,在数据交换场景中,导入的数据将仅具有“空位”,而不是沿着新的位级边界重新打包数据(因此来自 8 位字节计算机的 10 字节数据仍将占用10 位机器上的 10 个字节(但使用 100 位而不是 80 位)。

【讨论】:

当然,但是根据这个论点,memcpy 及其类似的也可以采用char * 参数而不是void *,因为所有内存都是字节。能够声明不应该直接使用的符号似乎非常好void @Dolda2000 memcpy 使用 void* 因为 C 没有运算符重载,所以您可以使用 memcpyshort*unsigned long long int* 而无需 CRT 拥有 @987654338 @、memcpylongmemcpychar @rowan.G 你是回复我还是多尔达?您可以使用 memcpy 而不进行任何强制转换。 @Dai to dolda,如果没有演员表,你将无法按照他的方式使用它【参考方案3】:

来自 ISO/IEC9899:

6.3.2.2 无效

1 void 表达式(具有 void 类型的表达式)的(不存在的)值不应 以任何方式使用,并且不得进行隐式或显式转换(无效除外) 应用于这样的表达。如果任何其他类型的表达式被评估为 void 表达式,其值或指示符被丢弃。 (一个 void 表达式被评估为它的 副作用。)

所以对于你的问题,他们为什么开始警告它:

因为他们开始能够检测到这种无效的 void 用法。

【讨论】:

以上是关于为啥 GCC 开始警告获取 void 表达式的地址?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 GCC 会警告这种隐式转换?

为啥gcc会有一个long long的警告?

为啥 gcc 给出警告:函数 qsort_r 的隐式声明?

为啥使用字符串初始化没有 const 的数组时 gcc 不给出警告?

PHP 7.1 - 为啥没有关于 void 返回值的警告?

使用 LLDB API 获取使用 ARM-GCC 编译的 elf 文件中表达式的地址