標籤:

最近看到陳碩的一本書提了一個問題,「編譯器如何處理inline函數中的static變數?」

最近看到陳碩的一本書提了一個問題,「編譯器如何處理inline函數中的static變數?」 我想如果編譯器進行展開的話就有了多份static變數,顯然這是不行的。那麼是不是編譯器就不會將這種inline函數展開?


§7.1.2/4 C++03 / C++11 / C++14

A static local variable in an extern inline function always refers to the same object.

很明確,如果是 extern inline 里的 static 變數,那麼就一定是同一個對象。所以問題中的「編譯器進行展開的話就有了多份static變數」不成立。

至於非「extern」的 inline 函數,已經是 internal linkage / no linkage 的函數了,不同翻譯單元里的版本之間也已經是不相干的東西,和題目中的疑問不衝突了。

p.s. 明明有標準文檔,為什麼要通過各種「實驗」來猜測結論呢?即便做實驗、看彙編,忙了半天最後發現結果與標準里的描述不符,那麼其實也只能是某個實現出錯了,而不是標準出錯了呀。編譯器也要按照基本法嘛。


inline specifier

An inline function is a function with the following properties:

1) There may be more than one definition of an inline function in the program as long as each definition appears in a different translation unit. For example, an inline function may be defined in a header file that is #include"d in multiple source files.

2) The definition of an inline function must be present in the translation unit where it is called (not necessarily before the point of call).

3) An inline function with external linkage (e.g. not declared static) has the following additional properties:

1) It must be declared inline in every translation unit.

2) It has the same address in every translation unit.

3) Function-local static objects in all function definitions are shared across all translation units (they all refer to the same object defined in one translation unit)

4) Types defined in all function definitions are also the same in all translation units.

5) String literals in all function definitions are shared (they are all the same string literal defined in just one translation unit)


我的回答完全錯過了重點。完全忘記了不同編譯單元的這件事orz

請參考 @邱昊宇 的規範回答。

讀過我的原回答的同學請讀下面的評論區 @Tim Shen 和 @bhuztez 的指正。

GCC文檔:

Vague Linkage - GCC Documentation

There are several constructs in C++ that require space in the object file but are not clearly tied to a single translation unit. We say that these constructs have 「vague linkage」. Typically such constructs are emitted wherever they are needed, though sometimes we can be more clever.

Inline Functions

Inline functions are typically defined in a header file which can be included in many different compilations. Hopefully they can usually be inlined, but sometimes an out-of-line copy is necessary, if the address of the function is taken or if inlining fails. In general, we emit an out-of-line copy in all translation units where one is needed. As an exception, we only emit inline virtual functions with the vtable, since it always requires a copy.

Local static variables and string constants used in an inline function are also considered to have vague linkage, since they must be shared between all inlined and out-of-line instances of the function.

一個相關的Clang警告:-Wstatic-local-in-inline

r178520 - Add -Wstatic-local-in-inline, which warns about using a static local

附送友好的文檔站:Which Clang Warning Is Generating This Message?

==================以下原回答====================

首先,C++里函數內的局部靜態變數是個語法糖。請跳傳送門:

c++函數局部靜態變數第二次被訪問的時候具體做了些什麼? - RednaxelaFX 的回答

本質上說,一個局部靜態變數會被提升為一個存儲位置及生命期跟全局變數相似,但是靜態作用域跟局部變數相似的一對變數。是的,不是一個變數,是一對變數。

以我在這邊給的例子看:

https://gist.github.com/rednaxelafx/cd279ad31c01593073a6

int g_counter = 0;

struct Dummy {
int value;

Dummy(): value(0) { g_counter++; }
};

int foo() {
static Dummy dummy;
return dummy.value++;
}

int main() {
foo();
foo();
return 0;
}

其中foo()可以展開為:(用點偽代碼,例如那個first_byte)

Dummy _foo_dummy; // only allocate space; ctor isn"t run as a normal global variable would
int64_t _guard_dummy; // guard variable for _foo_dummy

int foo() {
if (_guard_dummy.first_byte == 0) {
if (__cxa_guard_acquire(_guard_dummy)) {
try {
Dummy::Dummy(_foo_dummy); // run ctor now
}
catch (...) {
__cxa_guard_abort(_guard_dummy);
throw;
}
... queue object destructor with __cxa_atexit() ...;
__cxa_guard_release(_guard_dummy);
}
}
return _foo_dummy.value++;
}

(實際展開的樣子可以參考我上面給的Gist鏈接里的LLVM IR)

這個foo()函數,即使被內聯到多個不同的caller處,在同一編譯單元內它們仍然會共享同一對guard variable和實際的靜態局部變數,於是題主的擔心:

我想如果編譯器進行展開的話就有了多份static變數

是不成立的。


static變數只有一份,根據初始化與否存在bss段或者data段,和展開內聯函數沒有關係。我覺得編譯器需要注意的是對內聯函數里的變數做name mangling,避免和外圍的變數名衝突。


看下 elf 或者別的文件格式里 static 是怎麼放的,或者 隨便操作系統進程空間分布,大約就不會有這個疑問了吧


誰說展開了就會有多份...對象又不是存在代碼段的


1. Inliner一般工作在IR層面,而不是像宏那樣 在源碼層面做替換

2. 局部靜態變數和全局靜態變數在存儲上是一樣的,都在全局堆。只不過前端語法檢查會檢查他們的作用範圍。

換句話說,把局部靜態變數替換成全局靜態變數,對該函數本身沒有影響。而Inline只是把代碼複製了 數據還是一份 還在數據段/heap

所以對編譯器的inliner來說,就不是個事兒


機械化的理解了inline展開


static的作用是規定好的,而inline應該很弱吧,印象里比較新的編譯器似乎不怎麼操作inline,都自己優化了


inline只是一種建議。。。


直接告訴用戶不能內聯不就行了(逃

標準又沒有規定你不能這樣做。


推薦閱讀:

Mac系統下最好用的C++ IDE是XCode嗎 ?
關於鏈表的問題?
C/C++ 中 exit() 函數的參數到底有什麼意義?
怎樣改造一個有序的鏈表,使其能夠具有高效找到Node在鏈表中index(在鏈表中的第幾個結點)?

TAG:C | 編譯器 |