c语言 - MSVC:为什么"extern void x;"是"非法使用'void'类型"?



为什么有这段代码:

extern void x;

导致:

$ cl t555.c /std:c11 /Za
t555.c(1): error C2182: 'x': illegal use of type 'void'

这里什么是非法的?

上。用例:

$ cat t555a.c t555.p.S
#include <stdio.h>
extern void x;
int main(void)
{
printf("%pn", &x);
return 0;
}
.globl  x
x:
.space 4
$ gcc t555a.c -std=c11 -pedantic -Wall -Wextra -c && as t555.p.S -o t555.p.o && gcc t555a.o t555.p.o && ./a.exe
t555a.c: In function ‘main’:
t555a.c:7:20: warning: taking address of expression of type ‘void’
7 |     printf("%pn", &x);
|                    ^
0x1004010c0
$ clang t555a.c -std=c11 -pedantic -Wall -Wextra -c && as t555.p.S -o t555.p.o && clang t555a.o t555.p.o && ./a.exe
t555a.c:7:20: warning: ISO C forbids taking the address of an expression of type 'void' [-Wpedantic]
printf("%pn", &x);
^~
1 warning generated.
00007FF76E051120

这是一个有趣的案例。声明具有外部链接的 void 类型的标识符x似乎没有违反任何约束,但它几乎无法使用。

void"是无法完成的不完整对象类型"(C 2018 6.2.5 19)。当声明对象的标识符时没有链接,该类型必须"在其声明符末尾完成"(6.7 7)。但是,对于具有外部链接的标识符,情况并非如此;我们可以声明extern int a[]; extern struct foo b;并定义a,并在以后b,甚至在另一个翻译单元中也是如此。

如果不使用x,我认为它不会违反任何约束。如果程序尝试使用它,则 6.9 5 将适用:

。如果在表达式中使用了用外部链接声明的标识符(而不是作为结果为整数常量的sizeof_Alignof运算符的操作数的一部分),则在整个程序中的某个地方,标识符应该只有一个外部定义;否则,不得超过一个。

但是我们不能在 C 代码中定义x,因为它的类型不完整,并且其类型无法完成。只要它没有定义,我们就不能在表达式中使用x,而不是作为sizeof_Alignof的操作数,由于上述段落,我们也不能将其与sizeof_Alignof一起使用,因为这些运算符需要一个完整的类型。

我们可以想象x是在 C 之外定义的,并与这个 C 代码相关联。因此,某些程序集模块可能会为 C 代码未知的x提供定义。当然,C 代码不能在没有类型定义的情况下使用对象的值。但它可以使用x的地址。例如,它可以用作指针值的哨兵或其他标记。例如,我们可以将指向另一个例程的指针列表作为指针列表传递,其中子列表由&x分隔,整个列表的末尾由空指针标记。(所以两个子列表(&a&b&c)和(&d&e&f)将作为(void *[]) { &a, &b, &c, &x, &d, &e, &f, NULL };传递。

但是,使用 Clang 编译printf("%pn", &x);并使用-pedantic会产生错误消息"ISO C 禁止采用类型为'void'的表达式的地址"。这样做的核心原因似乎是 6.3.2.1 1 将void类型的对象排除为左值:

  • 左值是一个表达式(对象类型不是void),可能指定一个对象;

和 6.5.3.2 1 要求一元&的操作数为左值:

  • 一元&运算符的操作数应为函数指示符、[]或一元*运算符的结果,或指定对象的左值......

这可能是 C 标准中设计不完整的部分,因为它不排除const void成为左值,Clang 毫无怨言地编译了extern const void x; printf("%pn", &x);,但标准似乎没有理由在这方面区别对待const voidvoid

一方面,Microsoft可能已经得出结论,没有办法使用此x,因此在找到extern void x后立即为其发出诊断,而不是在代码尝试使用此x时发生错误。但是,虽然编译器可以自由地发出其他诊断消息,但它应该接受符合标准的程序。也就是说,对于符合 C 标准的编译器,诊断可能是警告,但可能不是阻止编译的错误。

补充说明

注意到一元&的约束允许"[]或一元*运算符的结果",我对此进行了测试:

static void foo(void *p)
{
printf("%pn", &*p);
}

在这里,*p本身是void类型的左值,这对于&是允许的,因为约束特别允许它,而&x似乎是一个非常相似的表达式,取void的地址,但约束不允许它,因为x既不是左值也不是*的结果。好奇。

最新更新