未初始化的 C 字符串没有警告
No warning with uninitialized C string
我目前想知道为什么我在 compilation/linking 一个小的 C 程序中没有收到来自 GCC 的错误。
我在 version.h
中声明了以下字符串:
const char* const VERSION;
在version.c
我设置了变量的初始化:
const char* const VERSION = "0.8 rev 213";
没问题。我可以在程序的其余部分使用该字符串。
如果 c 文件丢失,在 compilation/linking 期间不会发生错误,但程序在尝试访问变量时会失败并显示 SIGSEGV(当然)。
我设置变量 VERSION
的方法是否正确,或者是否有更好的方法?或者在 compilation/linking 期间是否有机会出错?
您在 header 中 定义了 (不只是声明)一个变量。
如果您从多个源文件中包含此 header,则行为是 未定义。这是标准的相关引述:
J.2 Undefined behavior
…
An identifier with external linkage is used, but in the program there does not exist exactly one external definition for the identifier, or the identifier is not used and there exist multiple external definitions for the identifier.
…
您依赖于 GCC-specific(实际上对许多编译器来说很常见,但仍然 non-standard)这里的行为,即合并重复的 暂定 数据定义。请参阅 -fcommon
和 -fno-common
GCC 编译标志的帮助。并非所有编译器都以这种方式运行。从历史上看,这是链接器的常见行为,因为这就是 Fortran 在 C 出现之前的工作方式。
假设此语言扩展,其中一个定义(具有显式初始化程序的定义)初始化变量以指向您的字符串文字。但是如果你省略这个定义,它将保持zero-initialised。也就是说,它将是一个常量空指针。不是很有用。
长话短说,永远不要那样做。为了在 header 中声明(但不定义)全局变量,请使用 extern
。如果你这样做,并试图在其他地方省略定义,你将 可能 得到一个链接器错误(尽管标准不需要诊断这种违规,所有已知的实现都会产生一个)。
在 version.h
中,您应该将 VERSION
声明为 extern
,例如
extern const char* const VERSION;
并且在 version.c
中你应该像
这样定义外部变量
const char* const VERSION = "0.8 rev 213";
编辑:- 此外,您还需要确保只有一个源文件定义了变量 VERSION
,而其他源文件声明了它 extern
并且您可以通过在源文件 VERSION.c
中定义它并将 extern
声明放在头文件 VERSION.h
.
中来实现
您的示例之所以有效,是因为 C(但不是 C++)的 Fortran-inspired(错误)功能称为 暂定定义(6.9.2p2),这通常是但非标准地扩展到多个文件。
暂定定义是不带 extern
且带
没有初始化器。在常见的实现中(双关语),暂定定义创建了一种特殊的符号,称为 common
符号。在链接期间,如果存在同名的常规符号,其他常用符号将成为对常规符号的引用,这意味着由于包含而在此处创建的翻译单元中的所有空 VERSION
将成为对常规符号的引用符号 const char* const VERSION = "0.8 rev 213";
。如果没有这样的常规符号,常用符号将合并到一个 zero-initalized 变量中。
我不知道如何使用 C 编译器获得警告。
这个
const char* const VERSION;
const char* const VERSION = "0.8 rev 213";
似乎与 gcc 一起工作,无论我尝试过什么(g++
不会接受它——C++ 没有暂定定义功能,它不喜欢 const 变量t 显式初始化)。但是您可以使用 -fno-common
(这是一个相当 "common"(强烈推荐)非标准选项(gcc、clang 和 tcc 都有)编译,然后如果 non-initialized 和初始化的 extern-less 声明在不同的翻译单元中。
示例:
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++ 的情况下,headers 中的所有变量声明都应包含 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";
我目前想知道为什么我在 compilation/linking 一个小的 C 程序中没有收到来自 GCC 的错误。
我在 version.h
中声明了以下字符串:
const char* const VERSION;
在version.c
我设置了变量的初始化:
const char* const VERSION = "0.8 rev 213";
没问题。我可以在程序的其余部分使用该字符串。
如果 c 文件丢失,在 compilation/linking 期间不会发生错误,但程序在尝试访问变量时会失败并显示 SIGSEGV(当然)。
我设置变量 VERSION
的方法是否正确,或者是否有更好的方法?或者在 compilation/linking 期间是否有机会出错?
您在 header 中 定义了 (不只是声明)一个变量。
如果您从多个源文件中包含此 header,则行为是 未定义。这是标准的相关引述:
J.2 Undefined behavior
…
An identifier with external linkage is used, but in the program there does not exist exactly one external definition for the identifier, or the identifier is not used and there exist multiple external definitions for the identifier.
…
您依赖于 GCC-specific(实际上对许多编译器来说很常见,但仍然 non-standard)这里的行为,即合并重复的 暂定 数据定义。请参阅 -fcommon
和 -fno-common
GCC 编译标志的帮助。并非所有编译器都以这种方式运行。从历史上看,这是链接器的常见行为,因为这就是 Fortran 在 C 出现之前的工作方式。
假设此语言扩展,其中一个定义(具有显式初始化程序的定义)初始化变量以指向您的字符串文字。但是如果你省略这个定义,它将保持zero-initialised。也就是说,它将是一个常量空指针。不是很有用。
长话短说,永远不要那样做。为了在 header 中声明(但不定义)全局变量,请使用 extern
。如果你这样做,并试图在其他地方省略定义,你将 可能 得到一个链接器错误(尽管标准不需要诊断这种违规,所有已知的实现都会产生一个)。
在 version.h
中,您应该将 VERSION
声明为 extern
,例如
extern const char* const VERSION;
并且在 version.c
中你应该像
const char* const VERSION = "0.8 rev 213";
编辑:- 此外,您还需要确保只有一个源文件定义了变量 VERSION
,而其他源文件声明了它 extern
并且您可以通过在源文件 VERSION.c
中定义它并将 extern
声明放在头文件 VERSION.h
.
您的示例之所以有效,是因为 C(但不是 C++)的 Fortran-inspired(错误)功能称为 暂定定义(6.9.2p2),这通常是但非标准地扩展到多个文件。
暂定定义是不带 extern
且带
没有初始化器。在常见的实现中(双关语),暂定定义创建了一种特殊的符号,称为 common
符号。在链接期间,如果存在同名的常规符号,其他常用符号将成为对常规符号的引用,这意味着由于包含而在此处创建的翻译单元中的所有空 VERSION
将成为对常规符号的引用符号 const char* const VERSION = "0.8 rev 213";
。如果没有这样的常规符号,常用符号将合并到一个 zero-initalized 变量中。
我不知道如何使用 C 编译器获得警告。
这个
const char* const VERSION;
const char* const VERSION = "0.8 rev 213";
似乎与 gcc 一起工作,无论我尝试过什么(g++
不会接受它——C++ 没有暂定定义功能,它不喜欢 const 变量t 显式初始化)。但是您可以使用 -fno-common
(这是一个相当 "common"(强烈推荐)非标准选项(gcc、clang 和 tcc 都有)编译,然后如果 non-initialized 和初始化的 extern-less 声明在不同的翻译单元中。
示例:
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++ 的情况下,headers 中的所有变量声明都应包含 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";