MSVC:为什么 "extern void x;" 是 "illegal use of type 'void'"?

MSVC: why "extern void x;" is "illegal use of type '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("%p\n", &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("%p\n", &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("%p\n", &x);
                   ^~
1 warning generated.
00007FF76E051120

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

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

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

… If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

但是我们不能在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("%p\n", &x); 并使用 -pedantic 会产生错误消息“ISO C 禁止获取类型 'void' 的表达式的地址”。我在 C 标准中看不到这样做的原因。它不是 & 运算符的约束(在 6.5.3.2 中)。 6.5.3.2 3 表示“……结果是指向其操作数指定的对象或函数的指针”,所以我们可以说没有对象 x,所以 &x 不能产生指向任何对象的指针或功能。但这有点牵强。

我认为Clang在这一点上可能是错误的。 6.7 4 说“同一作用域中引用同一对象或函数的所有声明应指定兼容的类型。”但是我没有看到禁止在一个翻译单元中声明 extern void x; 并在另一个翻译单元中声明 int x = 0; 。那么第一个翻译单元应该能够获取对象的地址x,它甚至应该能够将&x转换为char *并读写它的字节。

一方面,Microsoft 可能已经断定无法使用此 x,因此正在为其发布诊断。然而,虽然编译器可以自由地发出额外的诊断消息,但它应该接受一个符合要求的程序。也就是说,诊断可能是警告,但可能不是阻止编译的错误。