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

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