【问题标题】:C vs C++ global variable in header [duplicate]标头中的C vs C ++全局变量[重复]
【发布时间】:2019-08-19 14:38:57
【问题描述】:

我知道全局变量不应该在头文件中定义,我们应该使用extern 只在头文件中声明它们。

但我仍然尝试在以下标头 lib.h 中定义一个全局变量:

//lib.h

int i;

void add();

尝试在 C 和 C++ 中使用此标头时,我得到了一些有趣的结果

在 C 中,我在 main.clib.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.hlib.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++ 中?

【问题讨论】:

  • @Tzaulmen 我试过了,但它不起作用。
  • 这在两种语言中都是无效的,但至少 gcc(在 C 模式下)默认允许它 - 你使用什么编译器?
  • @aschepler 无效?为什么?这是一个坏主意,但有效……您的意思是使用未初始化的变量吗?
  • @EugeneSh。我不了解 C,但在 C++ 中,它违反了单一定义规则。
  • 标头被复制到 cpp 文件中(这就是 #include 所做的)。因此,如果将它包含在两个 cpp 文件中,则您刚刚定义了两个具有相同名称的不同全局变量。这实际上是您发布的错误消息,所以......这是一个很好的错误消息,一点也不奇怪。

标签: c++ c global-variables language-lawyer cross-language


【解决方案1】:

这种行为差异不是巧合,也不是编译器中的错误。这是对 C 和 C++ 标准的严格应用,它们在全局范围内拥有多个 int i; 的含义存在分歧。

在 C++ 中是无效的

在 C++ 中,int i; 是(未初始化的)对象的定义。 One Definition Rule (ODR) 不允许您多次定义同一个全局变量。

这是在 C++ 标准中定义的,在 [basic.def.odr]

部分

在 C 中有效

在 C 中,int i; 是一个暂定定义。对完全相同的全局变量进行多个临时声明是完全有效的。

这是在 C11 标准的 6.9.2 外部对象定义部分中定义的

/2: 具有文件范围的对象的标识符声明 没有初始化器,也没有存储类说明符或 存储类说明符静态,构成一个暂定的 定义。如果一个翻译单元包含一个或多个暂定 标识符的定义,并且翻译单元不包含 该标识符的外部定义,则行为正是 好像翻译单元包含该文件的文件范围声明 标识符,与翻译结束时的复合类型 单位,初始化器等于 0。

请注意,该子句的措辞并未说明在多个翻译单元中定义相同变量的情况。上面标准引用的最后一句话并不意味着它是每个文件中的不同变量(为此,您需要内部链接,与static)。它只是说行为就像变量的初始值为 0。

这种中立是有原因的:

  • 该标准将这种情况标识为未定义的行为:

    附件 J.2: 使用了带有外部链接的标识符,但在程序中有 不存在该标识符的确切一个外部定义,或 该标识符未使用且存在多个外部 标识符的定义

  • 但该标准也将具有多个定义的情况确定为广泛支持的公共扩展,只要这些定义不相互矛盾:

    附件 J.5.11: 对象标识符可能有多个外部定义,无论是否明确使用 关键字extern;如果定义不一致,或者不止一个 初始化,行为未定义

重要建议

因此,如果您打算编写可移植代码,我强烈建议在标头中使用extern,并在一个且仅一个编译单元中定义值。这是安全、清晰、明确的,并且适用于 C 和 C++。

【讨论】:

  • 这在 2 个不同的文件中有 2 个定义,这些文件在 C 中具有未定义的行为,但可以用作通用扩展名。 int i; 也没有内部链接。
  • 确实不是内部链接,我的错。我已编辑并添加了引文,表明在多个翻译单元中有多个暂定定义是完全有效的。
  • @LightnessRacesinOrbit 这是唯一谈论 C 的答案,但它是不正确的。见 C11 附录 J.2。其中列出:“使用了具有外部链接的标识符,但在程序中不存在完全一个标识符的外部定义,[...] (6.9)。”我>
  • @Christophe 该标准措辞是指在同一个 TU 内而不是跨 TU 内的多个临时声明。
  • 重点是当你研究 C 时,它的行为是未定义的。它与取消引用空指针或除以零或writing to string literals 没有任何区别。您认为它很好,只是因为它列在通用扩展中。
【解决方案2】:

当我在 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

【讨论】:

  • OP 询问为什么用 C 编译。
  • @LightnessRacesinOrbit 随意回答。他们还问为什么它不能用 C++ 编译。
【解决方案3】:

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

// lib.h
extern int i;
void add();
// lib.c or lib.cpp
#include "lib.h"
int i = 0;
void add()
{
  ++i;
}

【讨论】:

  • 如果编译器是 gcc,见gcc.gnu.org/onlinedocs/gcc/…。 gcc 在这里使用-fno-common 时遵循C 标准,但它的默认值通常是-fcommon
  • @aschepler 没有什么要遵守的,因为 C 标准只是说“行为是 未定义
  • 我认为整个问题是关于为什么它在 C 中编译而不是在 C++ 中 - 而不是如何使它在 C++ 中编译。
  • @AnttiHaapala 对,好点。
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 2020-06-11
    • 2022-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多