我目前想知道为什么在编译/链接小型C程序时没有收到GCC错误。
我在version.h
中声明了以下字符串:
const char* const VERSION;
在version.c
中,我设置了变量的初始化:
const char* const VERSION = "0.8 rev 213";
没问题。我可以在程序的其余部分中使用该字符串。
如果缺少c文件,则在编译/链接期间不会发生任何错误,但是当程序尝试访问该变量时,该程序将以SIGSEGV失败(当然)。
我设置变量VERSION
的方法是否正确,或者有更好的方法吗?还是有机会在编译/链接期间出现错误?
答案 0 :(得分:31)
您已在标头中定义了(不只是声明)。
如果您从多个源文件中包含此标头,则行为为未定义。这是标准中的相关报价:
J.2 Undefined behavior
...
使用了具有外部链接的标识符,但是在程序中,标识符并不完全存在一个外部定义,或者没有使用该标识符,并且标识符具有多个外部定义。
...
您在这里依赖于特定于GCC的(实际上对于许多编译器来说是通用的,但仍然是非标准的)行为,该行为合并了重复的临时数据定义。请参阅-fcommon
和-fno-common
GCC编译标志的帮助。并非所有的编译器都以这种方式运行。从历史上看,这是链接器的常见行为,因为这是Fortran在出现C之前的工作方式。
假定使用此语言扩展,其中一个定义(具有显式的初始化程序)将变量初始化为指向您的字符串文字。但是,如果省略此定义,它将保持零初始化。也就是说,它将是一个常量空指针。不太有用。
总而言之,永远不要那样做。为了在标头中声明(但未定义)全局变量,请使用extern
。如果这样做,并尝试在其他地方省略定义,您将可能出现链接器错误(尽管该标准不需要对此违例进行诊断,所有已知的实现都会产生一个错误)。
答案 1 :(得分:14)
您的示例之所以有效,是因为受C语言(但不是C ++)的Fortran启发(误)功能称为临时定义(6.9.2p2),该功能通常但非标准地扩展到多个文件。
暂定定义是不带extern
且带有
没有初始化器。在常见的实现方式(双关语)中,临时定义创建一种特殊的符号,称为common
符号。链接期间,在存在相同名称的常规符号的情况下,其他常见符号将成为常规符号的引用,这意味着在翻译单元中由于包含而在其中创建的所有空VERSION
都将成为常规符号的引用符号const char* const VERSION = "0.8 rev 213";
。如果没有这样的常规符号,则通用符号将合并为一个零斜杠变量。
我不知道如何使用C编译器对此发出警告。
此
const char* const VERSION;
const char* const VERSION = "0.8 rev 213";
无论我尝试了什么,似乎都可以使用gcc(g++
不会接受它-C ++没有暂定定义功能,并且它不喜欢未明确定义的const变量初始化)。但是您可以使用-fno-common
(这是一个相当“常见”(强烈推荐)的非标准选项(gcc,clang和tcc都具有该选项)进行编译),如果未初始化,则会出现链接器错误且初始化后的无外部声明位于不同的翻译单元中。
示例:
v.c:
const char * VERSION;
main.c
const char* VERSION;
int main(){}
编译和链接:
gcc main.c v.c #no error because of tentative definitions
g++ main.c v.c #linker error because C++ doesn't have tentative definitions
gcc main.c v.c -fno-common #linker error because tentative defs. are disabled
(出于C ++示例的缘故,我在示例中删除了第二个const
- C ++另外使const globals静态或类似的东西仅使临时定义功能的演示变得复杂。)
在禁用临时定义或使用C ++的情况下,标头中的所有变量声明中都应包含extern
version.h:
extern const char* const VERSION;
,您应该为每个全局变量只定义一个定义,并且该定义应该具有一个初始化程序,或者不包含extern
(某些编译器会在将extern
应用于初始化变量时发出警告)。
version.c:
#include "version.h" //optional; for type checking
const char* const VERSION = "0.8 rev 213";
答案 2 :(得分:6)
在version.h
中,您应将VERSION
声明为extern
,例如
extern const char* const VERSION;
在version.c
中,您应定义extern变量,如
const char* const VERSION = "0.8 rev 213";
编辑:-另外,您还需要确保只有一个源文件定义了变量VERSION
,而其他源文件声明了变量extern
,您可以通过在源文件VERSION.c
中定义它,并将extern
声明放在头文件VERSION.h
中。