我知道全局变量不应该在标头中定义,我们应该使用extern
只在标头中声明它们。
但我仍然尝试在以下标头lib.h
中定义一个全局变量:
//lib.h
int i;
void add();
尝试在 C 和 C++ 中使用此标头时,我得到了一些有趣的结果
在 C 中,我在main.c
和lib.c
中包含标头,它的编译和运行都很好:
//main.c
#include <stdio.h>
#include <stdlib.h>
#include "lib.h"
int main()
{
printf("%dn", i);
add();
printf("%dn", i);
return 0;
}
----
//lib.c
#include "lib.h"
void add(){
i++;
}
但是当我使用类似的代码(lib.h
和lib.cpp
与上面相同)在C++中运行时,它会给出有关具有多个定义的i
变量的错误消息:
//main.cpp
#include <iostream>
#include "lib.h"
using namespace std;
int main()
{
cout<<i<<endl;
add();
cout<<i<<endl;
return 0;
}
为什么它用 C 而不是C++编译?
这种行为差异不是巧合,也不是编译器中的错误。 这是对 C 和 C++ 标准的严格应用,它们在全球范围内拥有多个int i;
的含义存在分歧。
在C++中它是无效的
在C++中,int i;
是(未初始化的)对象的定义。一个定义规则 (ODR)不允许多次定义同一全局变量。
这在C++ 标准 [basic.def.odr]一节中定义。
在 C 中它是有效的
在 C 语言中,int i;
是一个暂定定义。 对完全相同的全局变量进行多个试探性声明是完全有效的。
这在 C11 标准第6.9.2节外部对象定义中定义:
/2:具有文件范围的对象的标识符声明 没有初始值设定项,也没有存储类说明符或 存储类说明符 static,构成暂定 定义。如果翻译单元包含一个或多个暂定 标识符的定义,并且翻译单元不包含 该标识符的外部定义,则行为恰好 好像翻译单元包含的文件范围声明 标识符,具有截至翻译末尾的复合类型 单位,初始值设定项等于 0。
请注意,此子句的措辞方式并未说明在多个翻译单元中定义相同变量的情况。 上面标准引用的最后一句话并不意味着它是每个文件中的不同变量(为此,您需要内部链接,带有static
)。 它只是说行为好像变量的初始值为 0。
这种中立是有原因的:
-
该标准将这种情况标识为未定义的行为:
附件J.2:使用具有外部链接的标识符,但在程序中 标识符不存在一个外部定义,或者 标识符未使用,并且存在多个外部 标识符的定义
-
但该标准还将具有多个定义的情况确定为广泛支持的常见扩展,只要这些定义不相互矛盾:
附件J.5.11:一个物体的标识符可能有多个外部定义,无论是否明确使用 关键字
extern
;如果定义不一致,或者不止一个 已初始化,行为未定义
重要建议
出于这个原因,如果你打算编写可移植的代码,我强烈建议在标头中使用extern
,并在一个且只有一个编译单元中定义值。 这是安全的,清晰的,明确的,并且适用于C和C++。
当我使用类似的代码在C++中运行时,它会给出有关具有多个定义的i变量的错误消息。为什么?
C++标准说:
[basic.def.odr] 每个程序应只包含该程序中使用的每个非内联函数或变量的一个定义,而不是丢弃的语句;无需诊断。
lib.cpp(我假设这是 c++ 中的"类似"源文件)和 main.cpp都定义了全局变量int i
。因此,该程序格式不正确。
解决方案:仅在标头中声明变量。在一个翻译单元中定义:
//lib.h
extern int i; // this declaration is not a definition
//lib.cpp
int i; // this declaration is a definition
不知道为什么它在 C 中工作,但在 C 和C++中都是错误的。尝试:
// lib.h
extern int i;
void add();
// lib.c or lib.cpp
#include "lib.h"
int i = 0;
void add()
{
++i;
}
因此,这是关于预处理器的棘手之处:当您使用 #define 时,它会复制和粘贴。这意味着main.cpp看到的int i;
与lib.cpp看到的int i;
不同。