为啥 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 没有运算符重载,所以您可以使用 memcpy
和 short*
或 unsigned long long int*
而无需 CRT 拥有 @987654338 @、memcpylong
、memcpychar
等
@rowan.G 你是回复我还是多尔达?您可以使用 memcpy
而不进行任何强制转换。
@Dai to dolda,如果没有演员表,你将无法按照他的方式使用它【参考方案3】:
来自 ISO/IEC9899:
6.3.2.2 无效
1 void 表达式(具有 void 类型的表达式)的(不存在的)值不应 以任何方式使用,并且不得进行隐式或显式转换(无效除外) 应用于这样的表达。如果任何其他类型的表达式被评估为 void 表达式,其值或指示符被丢弃。 (一个 void 表达式被评估为它的 副作用。)
所以对于你的问题,他们为什么开始警告它:
因为他们开始能够检测到这种无效的 void 用法。
【讨论】:
以上是关于为啥 GCC 开始警告获取 void 表达式的地址?的主要内容,如果未能解决你的问题,请参考以下文章