c++ - 标头中的 C vs C++ 全局变量
问题描述
我知道全局变量不应该在头文件中定义,而我们应该extern
只在头文件中声明它们。
但是我仍然尝试在以下标头中定义一个全局变量lib.h
:
//lib.h
int i;
void add();
尝试在 C 和 C++ 中使用此标头时,我得到了一些有趣的结果
在 C 中,我在main.c
和 in 中包含了头文件lib.c
,它编译和运行得很好:
//main.c
#include <stdio.h>
#include <stdlib.h>
#include "lib.h"
int main()
{
printf("%d\n", i);
add();
printf("%d\n", i);
return 0;
}
----
//lib.c
#include "lib.h"
void add(){
i++;
}
但是当我在 C++ 中使用类似的代码(lib.h
并且lib.cpp
与上面相同)运行它时,它会给出关于i
具有多个定义的变量的错误消息:
//main.cpp
#include <iostream>
#include "lib.h"
using namespace std;
int main()
{
cout<<i<<endl;
add();
cout<<i<<endl;
return 0;
}
为什么它用 C 而不是 C++ 编译?
解决方案
这种行为差异不是巧合,也不是编译器中的错误。int i;
这是对 C 和 C++ 标准的严格应用,它们在全局范围内 具有多个标准的含义存在分歧。
在 C++ 中它是无效的
在 C++ 中,int i;
是(未初始化的)对象的定义。单一定义规则 (ODR)不允许您多次定义同一个全局变量。
这是在 C++ 标准的[basic.def.odr]部分中定义的
在 C 中它是有效的
在 C 中,int i;
是一个暂定定义。对完全相同的全局变量进行多个临时声明是完全有效的。
这在 C11 标准第6.9.2 节外部对象定义中定义:
/2:具有文件范围的对象的标识符声明没有初始值设定项,并且没有存储类说明符或具有存储类说明符 static,构成暂定定义。如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型为翻译单元的末尾,初始化器等于 0。
请注意,该子句的措辞并未说明在多个翻译单元中定义相同变量的情况。上面标准引用的最后一句话并不意味着它是每个文件中的不同变量(为此,您需要内部链接, with static
)。它只是说行为就像变量的初始值为 0。
这种中立是有原因的:
该标准将这种情况标识为未定义的行为:
附件 J.2:使用了具有外部链接的标识符,但在程序中不存在该标识符的确切一个外部定义,或者未使用该标识符而该标识符存在多个外部定义
但该标准还将具有多个定义的情况确定为广泛支持的通用扩展,只要这些定义不相互矛盾:
附件 J.5.11:一个对象的标识符可能有多个外部定义,有或没有明确使用关键字
extern
; 如果定义不一致,或者不止一个被初始化,则行为未定义
重要建议
出于这个原因,如果你打算编写可移植的代码,我强烈建议extern
在头文件中使用,并在一个且只有一个编译单元中定义值。这是安全、清晰、明确的,并且适用于 C 和 C++。
推荐阅读
- excel - 如何在 VBA 中更改数组中数字的格式
- powershell - 获取在服务器上打开文件的计算机的 IP 地址
- javascript - 我无法使用 put 请求更新 obj 内的数组,如下所述
- axios - Nuxt 中的帐户激活
- javascript - 在服务器上实现 MySQL、PHP 和 Angular
- angular - 除了 [formControl] 之外,我什么时候需要 Angular 响应式表单的任何其他指令?
- reactjs - 在打印之前和之后更改状态以仅在打印时显示
- reactjs - 如何为列表的每一项添加独立的加载状态?
- rust - 无法从匹配臂返回对成员的可变引用
- debugging - 无法在 x64dbg 中修改程序集