在C ++中定义全局常量


81

我想在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)不能?

1
5与2:“ const”表示内部链接。将这个版本5标头包含在多个翻译单元中时,您不会违反“一个定义规则”。同样,const允许编译器进行“常量折叠”,而非const变量的值可能会更改。选项6是错误的。您还需要在cpp文件中使用“ extern”来强制进行外部链接,否则会出现链接器错误。选项6具有隐藏值的优点。但这也使恒定折叠成为不可能。
sellibitze 2010年

Answers:


32

(5)准确地说出您想说的话。另外,它使编译器大部分时间都可以对其进行优化。(6)另一方面,不会让编译器对其进行优化,因为编译器不知道您是否最终将对其进行更改。


1
OTOH,5在技术上是违反ODR的非法行为。但是,大多数编译器将忽略它。
乔尔2010年

嗯,我更喜欢认为它根本没有定义任何东西,我只是告诉编译器给数字起一个漂亮的名字。出于所有意图和目的,这就是(5),这意味着在运行时没有任何开销。
布林迪2010年

2
(5)是否违反了ODR?如果是,则优选(6)。在(6)的情况下,为什么编译器“不知道是否要更改”extern const int ...而且const int ...都是不变的吗?
D.Shawley'2

4
如果常量的类型不是基于int的,则AFAIK在5)和6)之间,仅允许6)。
Klaim

11
没有违反ODR的情况,默认情况下常量对象是静态的。
avakar 2010年

71

绝对可以使用选项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;

3
尝试包含header.hpp在多个源文件中时收到重新定义错误。
LRDPRDX

不知道为什么仍然要反对它-已经快十年了,但是如今我们已经constexpr为这种事情输入了枚举。
Nikolai Fetissov

23

(5)比(6)“更好”,因为它GLOBAL_CONST_VAR在所有翻译单元中都定义为整数常量表达式(ICE)。例如,您将可以在所有翻译单位中将其用作数组大小和大小写标签。在(6)的情况下,GLOBAL_CONST_VAR仅在定义该翻译单位的ICE中且仅在定义点之后才是ICE。在其他翻译单位中,它将无法用作ICE。

但是,请记住,(5)提供了GLOBAL_CONST_VAR内部链接,这意味着GLOBAL_CONST_VAR每个翻译单元中的“地址标识”将有所不同,即,在每个翻译单元中的“地址标识”&GLOBAL_CONST_VAR将为您提供不同的指针值。在大多数情况下,这无关紧要,但是,如果您需要具有一致的全局“地址标识”的常量对象,则必须使用(6),以牺牲常量的ICE-ness。处理。

同样,当常数的ICE值不是问题(不是整数类型)并且类型的大小变大(不是标量类型)时,(6)通常比(5)更好。

(2)不好,因为GLOBAL_CONST_VAR默认情况下in(2)具有外部链接。如果将其放在头文件中,通常会得到的多个定义GLOBAL_CONST_VAR,这是一个错误。const默认情况下,C ++中的对象具有内部链接,这就是(5)起作用的原因(也是如上所述,为什么GLOBAL_CONST_VAR每个翻译单元中都有一个独立的独立对象)。


从C ++ 17开始,您可以选择声明

inline extern const int GLOBAL_CONST_VAR = 0xFF;

在头文件中。这使您在所有翻译单元中都拥有一个ICE(就像方法(5)一样),同时保持全局地址身份GLOBAL_CONST_VAR-在所有翻译单元中,它将具有相同的地址。


8

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

constexpr int GLOBAL_CONST_VAR{ 0xff };

1
恕我直言,这是对此问题的唯一令人满意的解决方案。
lanoxx

5

如果它将是一个常数,则应将其标记为常数-这就是为什么我认为2不好。

编译器可以使用值的const性质来扩展一些数学运算,甚至扩展使用该值的其他运算。

在5到6-毫米之间选择;5对我来说感觉更好。

在6)中,该值不必要地从其声明中分离出来。

我通常会有一个或多个这些标头,它们仅在其中定义常量等,然后没有其他“聪明的”东西-轻巧的标头,可以轻松地包含在任何地方。


3
(6)不是不必要的超脱,而是有意选择。如果您有很多常量,那么如果不按(6)声明它们,则会在可执行文件中浪费大量空间。这可能发生在数学库中……浪费可能不到10万,但有时这很重要。(某些编译器还有其他解决方法,我认为MSVC具有“一次性”属性或类似属性。)
Dan Olson 2010年

使用(5),您不能真正确定它会保持const(您总是可以丢弃constness)。这就是为什么我仍然更喜欢枚举类型的原因。
fmuecke 2010年

@Dan Olson-很好-我的回答是基于以下事实:这里涉及的类型是int;但是当处理更大的值时,extern声明确实是一个更好的计划。
安德拉斯·佐尔坦

@fmuecke-是的,您是对的-在这种情况下,枚举值确实可以防止这种情况。但这是否意味着我们应该始终以这种方式保护我们的价值观免受写?如果程序员想滥用代码,那么(target_type *)((void *)&value)转换会在很多地方造成严重破坏,我们无法捕捉到,有时我们只需要对它们信任即可;甚至我们自己,不是吗?
安德拉斯·佐尔坦

@fmuecke不能通过程序更改声明为const的变量(尝试这样做是未定义的行为)。const_cast仅在未将原始变量声明为const的情况下定义(例如,将非const值作为const&传递给函数)。
大卫·斯通

5

要回答第二个问题:

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


4

C ++ 17inline变量

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

main.cpp

#include <cassert>

#include "notmain.hpp"

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

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

编译并运行:

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

GitHub上游

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

内联变量的C ++标准

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

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

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

内联变量实现

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

nm main.o notmain.o

其中包含:

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

man nmu

“ u”符号是唯一的全局符号。这是对ELF符号绑定的标准集合的GNU扩展。对于这样的符号,动态链接程序将确保在整个过程中只有一个使用此名称和类型的符号。

因此我们看到有专门的ELF扩展程序。

已在GCC 7.4.0,Ubuntu 18.04上测试。


2
const int GLOBAL_CONST_VAR = 0xFF;

因为它是一个常数!


1
而且不会像宏一样对待它,从而使调试变得更加容易。
kayleeFrye_onDeck

-1,将标头包含在多个源文件中时,这将导致重新定义警告/错误。同样,此答案与尼古拉·费迪索夫的答案重复。
lanoxx

1

这取决于您的要求。(5)最适合大多数常规用法,但通常会导致每个对象文件中的存储空间不断地占用。(6)在重要的情况下可以解决这个问题。

如果优先级是要保证永远不会分配存储空间,则(4)也是一个不错的选择,当然,它仅适用于整数常量。


1
#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.