首页 > 解决方案 > 标头中的 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++ 编译?

标签: c++cglobal-variableslanguage-lawyercross-language

解决方案


这种行为差异不是巧合,也不是编译器中的错误。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++。


推荐阅读