只在其cpp文件中使用的变量在头文件中声明和定义时出现多次定义错误

4

我正在将原本编译到一种芯片的代码移植到另一种芯片上。

出现的一个问题是多个定义错误。其中有些似乎是由于第一种芯片的链接器让我在跨多个源文件使用变量时可以懒惰地声明为extern。先前我根本没有使用extern(在something.h中声明和定义变量,然后在包括something.h的something.cpp和其他源文件中使用它),但它可以完美地编译和链接。

我认为我已经很好地解决了这些问题:现在我的共享变量具有以下模式:

Something.h

extern int foo;

Something.cpp

int foo = 0;

//using foo to do stuff

Main.cpp

#include "Something.h"

//using foo to do stuff

一切正常。

这里有一部分我不理解,也找不到任何答案,无论是在这里还是在谷歌上。我注意到同样的多重定义错误是由于在Something.h中声明和定义并且在Something.cpp中使用的变量所引起的。

Something.h有一个包含保护,因此我不认为这是由于Something.h在程序中的某个地方被多次包含造成的。

如果我将其声明为extern并在cpp文件中定义,错误就会消失,但我觉得这样做不对。我相信extern不需要用于连接Something.h和Something.cpp之间的变量。

非常感谢任何建议,我真的很想了解我在这里错过了什么。

(顺便说一句,我正在使用Arduino IDE为ESP32编译。)


1
Something.h只包含在Something.cpp中还是可能也包含在SomeOtherThing.cpp中?(考虑编译使用同一头文件的不同翻译单元时会发生什么。) - Mat
我想我可能开始理解你的意思了。我以为使用包含保护会防止这成为问题,但是你是在暗示因为每个文件在链接之前都是单独编译的,所以包含保护根本没有帮助吗?我不确定为什么会这样,因为那么我就看不到包含保护起作用的情况了? - JRVeale
1
预编译器中的 include guard 在使用 #include "Something.h" 时会针对每个源文件发挥作用。只有在一个文件中有多个(可能是间接的)#include "Something.h" 时,该 guard 才会生效。每个源文件都会创建相同变量的实例。 - harper
啊,当然,谢谢! - JRVeale
3个回答

9
如果您在头文件中声明变量:
#ifndef GLOBAL_H
#define GLOBAL_H

int foo = 0;

#endif

在每个头文件或翻译单元的包含中,都会创建一个新的整数实例。如你所提到的,为了避免这种情况,你需要在头文件中将该项声明为“extern”,并在实现文件中初始化它:
// .h
extern int foo;

// .cpp
int foo = 0

一种更符合C++的方法是这样的:
#ifndef GLOBAL_H
#define GLOBAL_H

struct Global {
    static int foo;
};
#endif

在你的cpp文件中:

#include "variables.h"

int Global::foo = 0;

C++17通过内联变量解决了这个问题,所以你可以这样做:

#ifndef GLOBAL_H
#define GLOBAL_H

inline int foo = 0;

#endif

更多信息请参见内联变量是如何工作的?


非常感谢,特别是对适当风格建议的指导。您“更C++方式”的说法让我坚信我已有一段时间认为该头文件需要较少的全局变量和更多的类封装的感觉。 - JRVeale
如果我在编写C代码时想要定义一个带有错误消息字符串的数组,应该怎么做呢?对我来说,将字符串定义放在枚举定义的下方是很自然的,而后者必须放在h文件中以便在外部使用。我的方式是否不好?什么是最佳实践? - MaxC

4

如果我将其声明为extern并在cpp文件中定义它,错误就会消失,

问题在于即使进行了包含保护等操作,该变量也会在每个编译单元中创建一次 - 但由于它是全局的,因此指向同一个变量。

为了解决这个问题,你需要在匿名命名空间中创建它。

Something.h

namespace {
  int foo = 0;
}

或者,使用静态关键字
Something.h
 static int foo = 0;

它们将在每个编译单元中创建不同的变量。


0
在C++17中,您可以通过内联变量解决此问题。内联变量避免了在头文件中定义变量的重复。在头文件中定义的变量将被视为在cpp文件中初始化。它们不会被复制。因此可以直接包含代码。

非常感谢如果您能提供一个样本。 - woodz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接