C++17的inline variable

我们在C++的头文件中,定义一个变量,比如:

int global_var = 10;

在多个.cc里面#inlucde这个头文件,编译会报“重复定义”的错误,而如果定义的是一个常量,比如:

const int global_const = 10;

却不会报错,这是为什么呢?

原因是,在C++中,const是隐式声明为static的,所以是内部链接(internal linkage)的,无论是在命名空间里面还是全局的。因为是内部链接,所以每一个#include了这个头文件的.cc文件,都会定义一份”拷贝“,这份拷贝有自己的符号表,所以不会有“重复定义”的错误。

C++17 n4659 standard draft 6.5 "Program and linkage":

3 A name having namespace scope (6.3.6) has internal linkage if it is the name of

  • (3.1) — a variable, function or function template that is explicitly declared static; or,
  • (3.2) — a non-inline variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage; or
  • (3.3) — a data member of an anonymous union.

C++11的附录中,给出了如下解释:

Appendix C (C++11, C.1.2) gives the rationale

Change: A name of file scope that is explicitly declared const, and not explicitly declared extern, has internal linkage, while in C it would have external linkage

Rationale: Because const objects can be used as compile-time values in C++, this feature urges programmers to provide explicit initializer values for each const. This feature allows the user to put const objects in header files that are included in many compilation units.

此外,附录中还解释了,为什么C++在这一点上面区别于C:

Annex C (informative) Compatibility, C.1.2 Clause 6: "basic concepts" gives the rationale why this was changed from C:

6.5 [also 10.1.7]

Change: A name of file scope that is explicitly declared const, and not explicitly declared extern, has internal linkage, while in C it would have external linkage.

Rationale: Because const objects may be used as values during translation in C++, this feature urges programmers to provide an explicit initializer for each const object. This feature allows the user to put const objects in source files that are included in more than one translation unit.

Effect on original feature: Change to semantics of well-defined feature.

Difficulty of converting: Semantic transformation.

How widely used: Seldom.

按照上面所说的,实际上,一个全局const常量,所占用的内存空间会有多份。C++17引入了内联变量(inline variable),内联变量是唯一的,即使有多个.cc使用它。搭配使用inline和constexpr,可以解决这个问题。有一点提醒下,inline是隐式声明为extern的,即为外部链接(external linkage)。

 C++17 N4659 standard draft10.1.6 "The inline specifier":

6 An inline function or variable with external linkage shall have the same address in all translation units.

这里我们来深究一下,C++17是如何实现inline constexpr只有一个内存地址的,示例代码如下:

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

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.cpp

#include "notmain.hpp"

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

Compile and run:

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

通过nm命令来查看符号表,

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 nm命令,可以得知u的含义:

"u" The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.

由此可以得知,有一个专门的 ELF extension 是为此设计的。

reference:

https://zh.cppreference.com/w/cpp/language/inline

https://stackoverflow.com/questions/30208685/how-to-declare-constexpr-extern

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章