为什么gcc允许类型为void(非指针)的extern声明?

时间:2012-04-06 03:58:07

标签: c gcc

为什么gcc允许类型为void的extern声明?这是一个扩展还是 标准C?这有可接受的用途吗?

我猜这是一个扩展,但我没有在下面提到它:
http://gcc.gnu.org/onlinedocs/gcc-4.3.6/gcc/C-Extensions.html

$ cat extern_void.c
extern void foo; /* ok in gcc 4.3, not ok in Visual Studio 2008 */
void* get_foo_ptr(void) { return &foo; }

$ gcc -c extern_void.c # no compile error

$ gcc --version | head -n 1
gcc (Debian 4.3.2-1.1) 4.3.2

将foo定义为void类型当然是编译错误:

$ gcc -c -Dextern= extern_void.c
extern_void.c:1: error: storage size of ‘foo’ isn’t known

为了进行比较,Visual Studio 2008在extern声明中给出了错误:

$ cl /c extern_void.c 
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

extern_void.c
extern_void.c(1) : error C2182: 'foo' : illegal use of type 'void'

4 个答案:

答案 0 :(得分:6)

奇怪的是(或者也许并不那么奇怪......)它让我觉得gcc接受这个是正确的。

如果这被声明为static而不是extern,则它将具有内部链接,并且适用§6.9.2/ 3:

  

如果对象的标识符声明是暂定定义并且具有内部标识符   联系,声明的类型不应是不完整的类型。

如果它没有指定任何存储类(在这种情况下为extern),则适用§6.7/ 7:

  

如果声明了一个没有链接的对象的标识符,则该对象的类型应在其声明符的末尾完成,或者在其init-declarator的末尾完成(如果它具有   初始化;在函数参数(包括在原型中)的情况下,需要完成的是调整后的类型(见6.7.5.3)。

我遇到其中任何一种情况,void都行不通,因为(§6.2.5/ 19):

  

void类型[...]是一种无法完成的不完整类型。

然而,

适用。这似乎只留下§6.7.2/ 2的要求,这似乎允许声明类型为void的名称:

  

每个声明中的声明说明符中至少应给出一个类型说明符,   并在每个结构声明和类型名称的说明符限定符列表中。每个清单   类型说明符应为以下集合之一(以逗号分隔,当有   在一条线上不止一套);类型说明符可能以任何顺序出现   与其他声明说明符混合。

     
      
  • 空隙
  •   
  •   
  • signed char
  •   
     

[...更多类型省略]

我不确定这是非常有意的 - 我怀疑void是真的用于派生类型之类的东西(例如,指向void的指针)或函数的返回类型,但我找不到任何直接指定限制的东西。

答案 1 :(得分:5)

我找到了宣布

的唯一合法用途
extern void foo;

foo是链接符号(链接器定义的外部符号),表示未指定类型的对象的地址。

这实际上很有用,因为链接符号通常用于传达内存的范围;即.text部分起始地址,.text部分长度等。

因此,使用这些符号的代码通过将它们转换为适当的值来记录它们的类型是很重要的。例如,如果foo实际上是内存区域的长度:

uint32_t textLen;

textLen = ( uint32_t )foo;

或者,如果foo是同一内存区域的起始地址:

uint8_t *textStart;

textStart = ( uint8_t * )foo;

在“C”中引用链接符号的唯一替代方法是将其声明为外部数组:

extern uint8_t foo[];

我实际上更喜欢void声明,因为它清楚地表明链接器定义的符号没有内在的“类型”。

答案 2 :(得分:1)

GCC(同样,LLVM C前端)绝对是错误的。 Comeau和MS似乎都报告了错误。

OP的片段至少有两个明确的UB和一个红鲱鱼:

从N1570起

[UB#1]在托管环境中缺少main

  

<强> J2。未定义的行为

     

[...]托管环境中的程序未使用其中一个指定的表单(5.1.2.2.1)定义名为main的函数。

[UB#2]即使我们忽略了上述内容,仍然存在明确禁止void表达式地址的问题:

  

6.3.2.1左值,数组和函数指示符

     

1左值是一个潜在的表达式(对象类型不是void)   指定一个对象; 64)

  

6.5.3.2地址和间接运营商

     

<强>约束

     1T他是一元&amp;的操作数。运算符应该是一个函数   指示符,[]或一元*运算符的结果,或 左值 的结果   指定一个不是位字段且未声明的对象   寄存器存储类说明符。

[注意:强调 lvalue 我的] 此外,标准中还有一节专门针对void

  

6.3.2.2无效

     

1 void表达式(具有void类型的表达式)的(不存在)值不得以任何方式使用,并且   隐式或显式转换(无效除外)不得应用   这样的表达。

文件范围定义是主表达式(6.5)。因此,采用foo表示的对象的地址。 BTW,后者调用UB。因此明确排除了这一点。 还有待解决的问题是,如果删除extern限定符使上述内容有效:

在我们的案例中,对于foo,根据§6.2.2/ 5:

  

5 [...]如果声明对象的标识符   有文件范围,没有存储类说明符,它的链接是   外部

即。即使我们遗漏了extern,我们仍然会遇到同样的问题。

答案 3 :(得分:1)

C的链接器交互语义的一个限制是它没有提供允许数字链接时间常量的机制。在某些项目中,静态初始值设定项可能需要包含在编译时不可用但在链接时可用的数值。在某些平台上,这可以通过在某处(例如在汇编语言文件中)定义标签来实现,该标签的地址(如果转换为int)将产生感兴趣的数值。然后可以在C文件中使用extern定义,使该事物的“地址”可用作编译时常量。

这种方法非常特定于平台(就像使用汇编语言的任何东西一样),但它使一些构造成为可能,否则会有问题。一个有点讨厌的方面是,如果标签在C中被定义为类似unsigned char[]的类型,那将传达这样的印象:地址可能被解除引用或对其执行算术。如果编译器接受void foo;,那么(int)&foo将使用与任何其他的相同的指针到整数语义将foo的链接器指定地址转换为整数。无效*。

我认为我之前没有使用void(我总是使用extern unsigned char[])但是如果将某些内容定义为是void会更清晰一个合法的扩展(C标准中没有任何内容要求任何地方都存在任何能力来创建一个链接符号,它可以用作除一个特定的非void类型之外的任何东西;在没有方法存在的平台上创建一个C的链接器标识符程序可以定义为extern void,编译器不需要允许这样的语法。