[英]Are static class members guaranteed to be initialized before `main` is called?
[英]Is static object guaranteed to be initialized
我試圖了解靜態對象的初始化。 假設你理解常量表達式和constexpr
,靜態初始化似乎很簡單。 動態初始化似乎有點棘手。
[basic.start.init / 4
實現定義是否在第一個main語句之前完成具有靜態存儲持續時間的非局部變量的動態初始化。 如果初始化延遲到第一個main語句之后的某個時間點,則應該在與要初始化的變量相同的轉換單元中定義的任何函數或變量的第一個odr-use(3.2)之前發生。
腳注34
具有靜態存儲持續時間的非局部變量必須初始化,即使它沒有使用odr-used(3.2,3.7.1)。
[basic.start.init] / 5
實現 - 定義是否在線程的初始函數的第一個語句之前完成具有靜態或線程存儲持續時間的非局部變量的動態初始化。 如果初始化被推遲到線程初始函數的第一個語句之后的某個時間點,它應該在任何變量的第一個odr-use(3.2)之前發生,其中線程存儲持續時間與變量在同一個轉換單元中定義要初始化。
我假設“線程的初始函數”是指main,而不僅僅是以std :: thread開頭的線程。
h1.h
#ifndef H1_H_
#define H1_H_
extern int count;
#endif
tu1.cpp
#include "h1.h"
struct S
{
S()
{
++count;
}
};
S s;
tu2.cpp
#include "h1.h"
int main(int argc, char *argv[])
{
return count;
}
tu3.cpp
#include "h1.h"
int count;
因此,如果編譯器推遲動態初始化,腳注34似乎表明必須在某個時刻初始化s
。 由於在轉換單元中沒有其他具有動態初始化的變量,因此沒有其他變量可用於強制初始化tu1中的變量。 在什么時候被s
保證已初始化?
主要保證返回1嗎? 還有,有什么方法可以改變這個程序,使其不再保證返回1? 或者,如果不能保證,有沒有辦法改變這個程序,以保證它?
我打破了代碼,以便s
的定義與main
翻譯單元不同。 這避免了main
是否使用odr的問題。 鑒於s
是翻譯單元中唯一的對象,是否保證main
將返回1?
我認為所有這些措辭都是為了描述動態加載庫中會發生什么,但沒有明確命名它們。
總結我如何解釋它:具有靜態存儲持續時間和動態初始化的非局部變量將:
main
之前,但可能在之后。 我將腳注34解釋為(但請記住,腳注不是規范性的):
當TU中的任何東西被ord-used使用時,每個具有靜態存儲持續時間的非局部變量都必須初始化,即使是沒有使用過的變量也是如此。
因此,如果存在未使用任何東西的TU,則其動態初始化可能不會發生。
extern int count;
struct S
{
S();
};
#include "h1.h"
int count;
S::S()
{
++count;
}
#include "h1.h"
S s;
#include "h1.h"
#include <stdio.h>
int main()
{
printf("%d\n", count);
}
這可以打印0或1:因為TU h2中的任何內容都不會被使用,所以當s
的代碼初始化完成時(如果有的話),它是未指定的。
當然,理智的編譯器會在main之前初始化s
,所以它肯定會打印1
:
$ g++ main.cpp h2.cpp h1.cpp -o test1
$ ./test1
1
現在,假設h2.cpp
位於共享庫中:
$ g++ -shared -fPIC h2.cpp -o h2.so
主文件現在是:
#include "h1.h"
#include <dlfcn.h>
#include <stdio.h>
int main()
{
printf("%d\n", count);
dlopen("./h2.so", RTLD_NOW);
printf("%d\n", count);
return 0;
}
編譯並運行:
$ g++ -shared -fPIC h2.cpp -o h2.so
$ g++ -rdynamic main.cpp h1.cpp -ldl -o test2
$ ./test2
0
1
看到? s
的初始化已被延遲! 好的部分是,如果沒有先加載它就不可能在動態加載庫中引用任何內容,加載它將觸發動態初始化。 一切都很好。
如果您認為使用dlopen
是作弊,請記住有些編譯器支持延遲加載共享庫(例如VC ++),其中加載庫的系統調用將在第一次需要時由編譯器自動生成。
如果不在定義中搜索正確的頁面,我可以說保證程序返回1.每次靜態或全局初始化都在main中的第一個命令之前完成。 全局變量首先初始化然后執行全局對象的構造函數。 函數/方法范圍內的靜態在首次使用之前初始化。 但是有一個陷阱:
int count;
struct A
{
A()
{
count=5;
}
};
struct B
{
B()
{
count=count*2;
}
};
A a;
B b;
void main(void)
{
return count;
}
如Ben Voigt的評論中所述,如果兩個實例都在同一翻譯單元中創建,則定義結果。 所以在我的示例中,結果是10.如果實例是在不同的文件中創建的(並且分別編譯為不同的.obj文件),則不會定義結果。
“在與要初始化的變量相同的翻譯單元中定義的任何函數或變量的第一次使用”包括要初始化的變量。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.