不會(huì)吧!全局變量在 main 前的初始化,竟然是靜態(tài)、動(dòng)態(tài)兩步走?
全局變量在 main 函數(shù)前初始化,這個(gè)大家都知道,但是,全局變量的初始化方式卻是一個(gè)容易被忽視但又至關(guān)重要的細(xì)節(jié),全局變量的初始化可以分為靜態(tài)初始化和動(dòng)態(tài)初始化兩種方式。

一、 什么是全局變量初始化?
全局變量是在所有函數(shù)體之外聲明的變量。
初始化是指為變量賦予其初始值的過(guò)程,它們的內(nèi)存空間在程序啟動(dòng)時(shí)就會(huì)被分配,并且它們的初始化過(guò)程發(fā)生在 main 函數(shù)執(zhí)行之前。
這個(gè)初始化過(guò)程可以分為兩個(gè)不同的階段:靜態(tài)初始化 (Static Initialization) 和 動(dòng)態(tài)初始化 (Dynamic Initialization)。
這里需要知道的是: static 局部變量 、static 類(lèi)成員變量 和全局變量它們都是具有靜態(tài)生命周期的變量!
二、 靜態(tài)初始化:編譯鏈接時(shí)的確定性
靜態(tài)初始化是全局變量初始化的第一個(gè)階段,它發(fā)生在程序加載之前,由編譯器和鏈接器在可執(zhí)行文件中預(yù)先安排。
這個(gè)階段的特點(diǎn)是:
(1) 初始化值必須常量表達(dá)式
靜態(tài)初始化的值必須是在編譯時(shí)就能完全確定的常量。這包括字面量(如 10, "hello")、const 常量、枚舉值,以及由這些常量組成的算術(shù)表達(dá)式等。(C++11及后續(xù)標(biāo)準(zhǔn)中,常量初始化被明確定義為靜態(tài)初始化的一部分,用于優(yōu)化常量表達(dá)式的處理。)
(2) 零初始化:
如果全局變量(或靜態(tài)變量)沒(méi)有被顯式地初始化,編譯器會(huì)對(duì)其進(jìn)行零初始化。這意味著整型變量會(huì)被初始化為 0,浮點(diǎn)型為 0.0,指針為 nullptr (或 NULL),布爾值為 false,聚合類(lèi)型(如數(shù)組、結(jié)構(gòu)體)的每個(gè)成員都會(huì)被遞歸地零初始化。
(3) 實(shí)現(xiàn)方式:
編譯器通常會(huì)將靜態(tài)初始化的值直接寫(xiě)入可執(zhí)行文件的特定段(如 .data 段用于顯式初始化的非零值,.bss 段用于零初始化的值)。程序加載時(shí),這些段的內(nèi)容會(huì)被直接映射到內(nèi)存中,無(wú)需執(zhí)行額外的代碼。
簡(jiǎn)單來(lái)說(shuō),靜態(tài)初始化就像是在"設(shè)計(jì)圖紙"階段就已經(jīng)確定好的固定參數(shù),直接"印"在了最終的產(chǎn)品上。
示例:
c++復(fù)制代碼
#include
int g_zero_initialized; // 靜態(tài)初始化:零初始化為 0
int g_explicit_static = 10; // 靜態(tài)初始化:用常量表達(dá)式 10 初始化
const char* g_message = "Hello"; // 靜態(tài)初始化:用字符串字面量(常量)初始化
const int g_const_val = 5 * 2; // 靜態(tài)初始化:用常量表達(dá)式初始化
int main() {
std::cout << "g_zero_initialized: " << g_zero_initialized << std::endl; // 輸出 0
std::cout << "g_explicit_static: " << g_explicit_static << std::endl; // 輸出 10
std::cout << "g_message: " << g_message << std::endl; // 輸出 Hello
std::cout << "g_const_val: " << g_const_val << std::endl; // 輸出 10return 0;
}靜態(tài)初始化的局限性在于它只能處理常量表達(dá)式(例如示例當(dāng)中的 5 * 2)。如果初始值依賴(lài)于運(yùn)行時(shí)計(jì)算(如函數(shù)調(diào)用或隨機(jī)數(shù)生成),就無(wú)法使用靜態(tài)初始化,轉(zhuǎn)而需要?jiǎng)討B(tài)初始化。
三、 動(dòng)態(tài)初始化:程序啟動(dòng)時(shí)的靈活性
靜態(tài)初始化只能處理常量表達(dá)式,但有時(shí)我們需要用更復(fù)雜的方式來(lái)初始化全局變量,比如調(diào)用函數(shù)、使用非 const 全局變量的值,或者初始化一個(gè)類(lèi)的全局對(duì)象并調(diào)用其構(gòu)造函數(shù)。這時(shí),動(dòng)態(tài)初始化 就派上用場(chǎng)了。
動(dòng)態(tài)初始化發(fā)生在靜態(tài)初始化完成之后,main 函數(shù)開(kāi)始執(zhí)行之前。
它的特點(diǎn)是:
(1) 初始化值可以是非常量:
動(dòng)態(tài)初始化允許使用函數(shù)調(diào)用、其他變量的值(即使它們本身是動(dòng)態(tài)初始化的)或者需要運(yùn)行時(shí)計(jì)算的表達(dá)式來(lái)初始化全局變量。
(2) 執(zhí)行時(shí)機(jī):
在程序啟動(dòng)過(guò)程中,靜態(tài)初始化完成后,但在 main 函數(shù)執(zhí)行前,會(huì)有一段特殊的啟動(dòng)代碼(runtime startup code)負(fù)責(zé)執(zhí)行這些動(dòng)態(tài)初始化操作。
(3) C++ 類(lèi)對(duì)象:
全局類(lèi)對(duì)象的構(gòu)造函數(shù)調(diào)用通常屬于動(dòng)態(tài)初始化(除非構(gòu)造函數(shù)非常簡(jiǎn)單且滿足特定條件,可能被優(yōu)化為靜態(tài)初始化)
簡(jiǎn)單來(lái)說(shuō),動(dòng)態(tài)初始化就像是在產(chǎn)品組裝完成后、正式使用前,進(jìn)行的"開(kāi)機(jī)設(shè)置"或"首次配置"。
示例:
#include <iostream>
#include <string>
#include <cmath>
#include <ctime>
// 靜態(tài)初始化(零初始化)
int g_some_value;
// 動(dòng)態(tài)初始化 - 需要運(yùn)行時(shí)計(jì)算
double g_pi = acos(-1.0); // acos不是常量表達(dá)式
// 動(dòng)態(tài)初始化 - 需要調(diào)用函數(shù)
time_t g_start_time = time(nullptr); // time()函數(shù)調(diào)用
// 動(dòng)態(tài)初始化 - 依賴(lài)其他全局變量 (可能引發(fā)順序問(wèn)題)
// int g_dependent_value = g_some_value + 5; // 如果g_some_value也是動(dòng)態(tài)初始化,需注意順序
// C++ 類(lèi)對(duì)象的動(dòng)態(tài)初始化
classMyClass {
public:
MyClass(const std::string& name) : name_(name) {
std::cout << "構(gòu)造函數(shù)執(zhí)行: " << name_ << std::endl;
}
std::string getName()const{ return name_; }
private:
std::string name_;
};
std::string get_username(){
// 模擬獲取用戶名
return"默認(rèn)用戶名";
}
MyClass g_my_object(get_username()); // 調(diào)用構(gòu)造函數(shù)和get_username(),動(dòng)態(tài)初始化
intmain(){
std::cout << "main 函數(shù)開(kāi)始執(zhí)行..." << std::endl;
std::cout << "g_pi: " << g_pi << std::endl;
std::cout << "g_start_time: " << g_start_time << std::endl;
// std::cout << "g_dependent_value: " << g_dependent_value << std::endl;
std::cout << "g_my_object name: " << g_my_object.getName() << std::endl;
// 即使 g_some_value 在 main 之前被動(dòng)態(tài)初始化賦值(如果它是動(dòng)態(tài)的話)
// 這里訪問(wèn)它時(shí),它已經(jīng)完成了初始化
g_some_value = 100; // 在 main 中修改
std::cout << "g_some_value in main: " << g_some_value << std::endl;
return0;
}輸出:(VS2022)
構(gòu)造函數(shù)執(zhí)行: 默認(rèn)用戶名
main 函數(shù)開(kāi)始執(zhí)行...
g_pi: 3.14159
g_start_time: 1744354404
g_my_object name: 默認(rèn)用戶名
g_some_value in main: 100四、 為什么區(qū)分靜態(tài)和動(dòng)態(tài)初始化
區(qū)分這兩個(gè)階段主要是為了效率和靈活性的平衡:
- 靜態(tài)初始化效率高: 它在編譯時(shí)確定值,程序加載時(shí)映射到內(nèi)存,不增加程序啟動(dòng)時(shí)間。對(duì)于大量簡(jiǎn)單的全局?jǐn)?shù)據(jù),這是最優(yōu)的方式。
- 動(dòng)態(tài)初始化提供靈活性: 它允許進(jìn)行復(fù)雜的初始化操作,適應(yīng)更多場(chǎng)景,但會(huì)稍微增加程序啟動(dòng)的開(kāi)銷(xiāo)。
五、 靜態(tài)初始化順序?yàn)?zāi)難
這個(gè)概念里面的靜態(tài)指的是生命周期:靜態(tài)存儲(chǔ)期(指的是變量的生命周期從程序開(kāi)始時(shí)分配內(nèi)存,直到程序結(jié)束時(shí)才釋放)
動(dòng)態(tài)初始化的一個(gè)潛在問(wèn)題是初始化順序。在不同的編譯單元(不同的 .cpp 文件)中定義的全局變量,它們的動(dòng)態(tài)初始化順序在 C++ 標(biāo)準(zhǔn)中并沒(méi)有嚴(yán)格規(guī)定。如果你在一個(gè)編譯單元的動(dòng)態(tài)初始化中,依賴(lài)了另一個(gè)編譯單元中需要?jiǎng)討B(tài)初始化的全局變量,就可能因?yàn)楹笳叩某跏蓟形赐瓿啥鲥e(cuò),這就是所謂的"靜態(tài)初始化順序?yàn)?zāi)難"。
避免方法:
- 盡量使用靜態(tài)初始化: 如果可能,優(yōu)先使用常量表達(dá)式進(jìn)行靜態(tài)初始化。
- 局部靜態(tài)變量: 將全局變量改為函數(shù)內(nèi)的靜態(tài)變量,利用其首次調(diào)用時(shí)才初始化的特性來(lái)保證依賴(lài)關(guān)系。
MyClass& get_global_object()
{
static MyClass instance(get_username()); // 在首次調(diào)用時(shí)才進(jìn)行動(dòng)態(tài)初始化
return instance;
}六、 靜態(tài) vs 動(dòng)態(tài)初始化
特性 | 靜態(tài)初始化 | 動(dòng)態(tài)初始化 |
初始化時(shí)機(jī) | 編譯時(shí)(概念上) | 運(yùn)行時(shí)(程序啟動(dòng)時(shí)) |
初始值類(lèi)型 | 常量表達(dá)式 | 可能涉及函數(shù)調(diào)用或運(yùn)行時(shí)計(jì)算 |
性能開(kāi)銷(xiāo) | 幾乎無(wú)開(kāi)銷(xiāo) | 可能有運(yùn)行時(shí)開(kāi)銷(xiāo) |
適用場(chǎng)景 | 固定值,如配置參數(shù) | 依賴(lài)環(huán)境或動(dòng)態(tài)計(jì)算的值 |
潛在問(wèn)題 | 無(wú) | 初始化順序問(wèn)題 |
總體來(lái)說(shuō),我覺(jué)得我們開(kāi)發(fā)當(dāng)中要注意這幾點(diǎn):
- 第一盡量使用靜態(tài)初始化以提高性能并避免初始化順序問(wèn)題。
- 第二如果必須依賴(lài)運(yùn)行時(shí)環(huán)境,確保初始化邏輯簡(jiǎn)單且無(wú)依賴(lài)關(guān)系。
- 第三對(duì)于復(fù)雜的初始化需求,可以將邏輯封裝到函數(shù)中,并在程序啟動(dòng)時(shí)顯式調(diào)用。
七、 總結(jié)
- 全局變量的初始化是一個(gè)分為靜態(tài)初始化和動(dòng)態(tài)初始化的有序過(guò)程,發(fā)生在 main 函數(shù)執(zhí)行之前。
- 靜態(tài)初始化處理常量表達(dá)式和零初始化,在編譯鏈接時(shí)確定,效率高。
- 動(dòng)態(tài)初始化處理非常量表達(dá)式、函數(shù)調(diào)用和類(lèi)對(duì)象構(gòu)造,在程序啟動(dòng)時(shí)執(zhí)行,靈活性強(qiáng)。
























