-3

我知道全局变量不应该在头文件中定义,而我们应该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++ 编译?

4

4 回答 4

2

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

于 2019-03-28T18:28:46.360 回答
1

当我用类似的代码在 C++ 中运行它时,它会给出一条关于 i 变量具有多个定义的错误消息。这是为什么?

C++ 标准说:

[basic.def.odr] 每个程序都应包含一个定义,该定义在被丢弃的语句之外的程序中使用 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
于 2019-03-28T18:26:47.000 回答
0

不知道为什么它在 C 中工作,但在 C 和 C++ 中都是错误的。尝试:

// lib.h
extern int i;
void add();
// lib.c or lib.cpp
#include "lib.h"
int i = 0;
void add()
{
  ++i;
}
于 2019-03-28T18:23:29.193 回答
-2

所以,预处理器的棘手之处在于:当您使用#define 时,它​​会复制和粘贴。这意味着int i;main.cpp 看到的与int i;lib.cpp 看到的不同。

于 2019-03-28T18:25:44.357 回答