在C++中定义全局常量

dim*_*mba 75 c++

我想在C++中定义一个常量,以便在几个源文件中可见.我可以想象以下方法在头文件中定义它:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. 一些功能可以恢复价值(例如int get_GLOBAL_CONST_VAR())
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; 并在一个源文件中 const int GLOBAL_CONST_VAR = 0xFF;

选项(1) - 绝对不是您想要使用的选项

选项(2) - 使用头文件在每个目标文件中定义变量的实例

选项(3) - 在大多数情况下,IMO过度杀戮

选项(4) - 在许多情况下可能不好,因为枚举没有具体类型(C++ 0X将增加定义类型的可能性)

所以在大多数情况下我需要在(5)和(6)之间进行选择.我的问题:

  1. 你更喜欢(5)或(6)?
  2. 为什么(5)没问题,而(2)不是?

Nik*_*sov 65

绝对使用选项5 - 它是类型安全的并且允许编译器进行优化(不要获取该变量的地址:)如果它在标题中 - 将其粘贴到命名空间中以避免污染全局范围:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants

} // namespace constants

// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;
Run Code Online (Sandbox Code Playgroud)

  • 尝试在几个源文件中包含`header.hpp`时,我得到重定义错误. (3认同)
  • 不知道为什么这仍然得到赞成 - 已经快十年了,但现在我们有了“constexpr”和类似的类型枚举。 (2认同)

Bli*_*ndy 30

(5)准确地说出你想说的话.此外,它允许编译器在大多数情况下优化它.(6)另一方面不会让编译器优化它,因为编译器不知道你最终是否会改变它.

  • 没有ODR违规,默认情况下常量对象是静态的. (10认同)
  • AFAIK,介于5)和6)之间,当常量的类型不是基于int时,仅允许6). (4认同)
  • (5) 是否违反了 ODR?如果是,则优选 (6)。为什么编译器_“不知道你是否会改变它”_在(6)的情况下?`extern const int ...` 和 `const int ...` 都是常量,不是吗? (2认同)

AnT*_*AnT 23

(5)比(6)"更好",因为它GLOBAL_CONST_VAR在所有翻译单元中定义为积分常数表达式(ICE).例如,您可以在所有翻译单元中将其用作数组大小和案例标签.在(6)的情况下,GLOBAL_CONST_VAR仅在该转换单元中定义ICE并且仅在定义点之后.在其他翻译单元中,它不能用作ICE.

但是,请记住(5)给出GLOBAL_CONST_VAR内部链接,这意味着GLOBAL_CONST_VAR每个翻译单元中的"地址标识" 将不同,即在每个翻译单元&GLOBAL_CONST_VAR中将给出不同的指针值.在大多数用例中,这并不重要,但如果你需要一个具有一致全局"地址标识"的常量对象,那么你必须使用(6),牺牲常量的ICE-ness.处理.

此外,当常量的ICE不是问题(不是整数类型)并且类型的大小变大(不是标量类型)时,则(6)通常变得比(5)更好的方法.

(2)不正常,因为GLOBAL_CONST_VARin(2)默认有外部链接.如果你把它放在头文件中,你通常会得到多个定义GLOBAL_CONST_VAR,这是一个错误.const默认情况下,C++中的对象具有内部链接,这就是为什么(5)工作(这就是为什么,正如我上面所说,你GLOBAL_CONST_VAR在每个翻译单元中得到一个独立的,独立的).


Cir*_*四事件 10

C++17inline变量

这个很棒的 C++17 功能使我们能够:

  • 每个常量仅使用一个内存地址即可方便地使用
  • 将其存储为constexpr如何声明 constexpr extern?
  • 在一个标题的一行中执行此操作

主程序

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}
Run Code Online (Sandbox Code Playgroud)

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif
Run Code Online (Sandbox Code Playgroud)

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main
Run Code Online (Sandbox Code Playgroud)

GitHub 上游.

另请参阅:内联变量如何工作?

内联变量的 C++ 标准

C++ 标准保证地址相同。C++17 N4659 标准草案 10.1.6“内联说明符”:

6 具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。

cppreference https://en.cppreference.com/w/cpp/language/inline解释说,如果static未给出,则它具有外部链接。

内联变量的实现

我们可以观察它是如何实现的:

nm main.o notmain.o
Run Code Online (Sandbox Code Playgroud)

其中包含:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i
Run Code Online (Sandbox Code Playgroud)

man nmu

“u”符号是全球独一无二的符号。这是 ELF 符号绑定标准集的 GNU 扩展。对于这样的符号,动态链接器将确保在整个过程中只有一个具有该名称和类型的符号正在使用。

所以我们看到有一个专门的 ELF 扩展用于此。

在 GCC 7.4.0、Ubuntu 18.04 上测试。


小智 6

如果您使用C ++ 11或更高版本,请尝试使用编译时常量:

constexpr int GLOBAL_CONST_VAR{ 0xff };
Run Code Online (Sandbox Code Playgroud)


And*_*tan 5

如果它将是一个常数,那么你应该将它标记为常数 - 这就是为什么2在我看来是不好的.

编译器可以使用值的const特性来扩展一些数学,以及使用该值的其他操作.

选择5到6 - hmm; 5对我来说感觉更好.

在6)中,该值与其声明不必要地脱离.

我通常会有一个或多个这些标题只定义其中的常量等,然后没有其他"聪明"的东西 - 很好的轻量级标题,可以很容易地包含在任何地方.

  • (6)不是不必要的脱离,这是一个有意的选择.如果你有很多很大的常量,如果没有像(6)那样声明它们,你会在可执行文件中浪费大量空间.这可能发生在数学图书馆......浪费可能低于10万,但即便如此也很重要.(有些编译器有其他方法来解决这个问题,我认为MSVC具有"一次"属性,或类似的东西.) (3认同)

MSa*_*ers 5

回答你的第二个问题:

(2) 是非法的,因为它违反了单一定义规则。它GLOBAL_CONST_VAR在包含它的每个文件中定义,即不止一次。(5) 是合法的,因为它不受单一定义规则的约束。每个GLOBAL_CONST_VAR都是一个单独的定义,在包含它的那个文件中是本地的。当然,所有这些定义共享相同的名称和值,但它们的地址可能不同。