【C++】全局變量/靜態變量的初始化時機

提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔

文章目錄

  • 一、全局變量下斷點調試
    • 1. `int a = 10;` —— 不能卡住斷點
    • 2. `static int b;` —— 不能卡住斷點
    • 3. `MyClass c;` —— 可以卡住斷點
    • 4. `static MyClass d;` —— 可以卡住斷點
    • 總結
  • 二、靜態變量初始化時機
    • 1. 局部靜態變量
    • 2. 與全局變量對比
      • 1. 全局變量的初始化時機
        • (1)靜態初始化階段(編譯期/加載期)
        • (2)動態初始化階段(運行期,main函數執行前)
      • 2. 局部靜態變量的初始化時機
        • (1)初始化觸發條件
        • (2)初始化階段(運行期,函數首次調用時)
      • 3. 關鍵差異對比
      • 4. 示例驗證
      • 總結
    • 3.同為靜態存儲區為什么初始化時機不同
  • 后續


一、全局變量下斷點調試

現有如下代碼

#include <iostream>int a = 10;
static int b;
MyClass c;
static MyClass d;int main() 
{return 0;
}

首先,全局變量的初始化分為零初始化和動態初始化。對于基本類型,比如 int a=10,這是常量初始化,屬于靜態初始化的一部分,通常在編譯時就完成,可能不會生成對應的執行代碼,所以調試器可能無法在定義處下斷點,因為那里沒有可執行的指令。

然后,static int b,全局靜態變量,默認初始化(零初始化),也是在程序加載時完成,同樣可能沒有對應的執行代碼,所以斷點可能無效。

接下來是自定義類的全局對象 c 和 d。類對象的初始化需要執行構造函數,這屬于動態初始化,發生在程序啟動階段(main 函數之前),由運行時庫調用構造函數。這時候會有對應的執行代碼(調用構造函數的指令),所以在定義處下斷點,調試器可能能捕捉到這個時機,因為構造函數的調用是有執行步驟的。

不過需要注意,調試器能否在全局變量定義處下斷點,取決于是否有對應的代碼位置。對于基本類型的初始化,因為是編譯期或加載期完成,沒有指令執行,所以斷點無效。而類對象因為需要執行構造函數,會有代碼執行,所以斷點可能有效。

另外,靜態全局變量(b 和 d)和非靜態全局變量(a 和 c)的區別主要在作用域(文件內還是全局),但初始化時機類似,基本類型的靜態初始化沒有執行代碼,類對象的動態初始化有執行代碼。

所以總結:int a 和 static int b 是基本類型,初始化在編譯或加載時,無執行代碼,斷點不能卡住;MyClass c 和 static MyClass d 需要執行構造函數,有執行代碼,斷點可以卡住。

在全局變量定義處設置斷點能否“卡住”調試,取決于變量初始化過程是否會生成可被調試器捕獲的執行代碼。具體分析如下:

1. int a = 10; —— 不能卡住斷點

  • 原因
    基本類型全局變量的初始化(如int a=10)屬于靜態初始化(編譯期即可確定初始值),由編譯器在編譯階段直接將初始值寫入數據段,程序加載時直接從二進制文件中讀取該值,不會生成單獨的執行代碼(沒有指令來“執行”這個初始化)。
    調試器需要有對應的機器指令才能設置斷點,而這里沒有可執行的代碼,因此斷點無效。

2. static int b; —— 不能卡住斷點

  • 原因
    全局靜態變量(static修飾的全局變量)的初始化規則與普通全局變量一致。未顯式初始化的基本類型(如int b)會進行零初始化(值為0),同樣在編譯期/程序加載階段完成,無執行代碼
    調試器無法在沒有指令的位置斷住,因此斷點無效。

3. MyClass c; —— 可以卡住斷點

  • 原因
    自定義類的全局對象初始化需要執行構造函數,這屬于動態初始化(運行時執行代碼)。程序啟動階段(main函數之前),運行時庫會自動調用MyClass的構造函數來初始化c,這個過程會生成對應的執行代碼(調用構造函數的指令)。
    調試器可以在構造函數執行的對應位置斷住,因此斷點有效。

4. static MyClass d; —— 可以卡住斷點

  • 原因
    全局靜態類對象(static MyClass d)的初始化邏輯與普通全局類對象一致:需要執行構造函數,屬于動態初始化,在main函數之前由運行時庫觸發,會生成執行代碼
    雖然static限定了其作用域(僅當前文件可見),但初始化過程仍有可被調試器捕獲的指令,因此斷點有效。

總結

  • 不能卡住斷點int a = 10;static int b;(基本類型,靜態初始化,無執行代碼)。
  • 可以卡住斷點MyClass c;static MyClass d;(類對象,動態初始化,需執行構造函數,有可調試的執行代碼)。

核心區別在于:基本類型的全局變量初始化在編譯/加載階段完成,無執行代碼;而類對象的全局變量需要運行時執行構造函數,存在可被調試器捕獲的代碼指令。

二、靜態變量初始化時機

1. 局部靜態變量

在C++中,局部靜態變量(包括自定義類的靜態局部對象)的初始化時機和特性如下:

  1. 初始化時機
    局部靜態變量在函數第一次被調用時進行初始化,且僅初始化一次。
    對于自定義類的靜態局部對象,其構造函數會在這個時機執行。

  2. 未調用函數的情況
    如果從未調用該函數,其中的靜態局部對象不會被初始化,其構造函數也不會執行

  3. 生命周期
    一旦初始化完成,局部靜態變量的生命周期會延續到整個程序結束(與全局變量類似),在main函數退出后才會執行析構函數(如果是類對象)。

示例代碼說明:

#include <iostream>class MyClass {
public:MyClass() {std::cout << "MyClass構造函數執行" << std::endl;}~MyClass() {std::cout << "MyClass析構函數執行" << std::endl;}
};void func() {static MyClass obj; // 靜態局部對象std::cout << "func()被調用" << std::endl;
}int main() {std::cout << "main()開始" << std::endl;func(); // 第一次調用func(),觸發obj的構造func(); // 第二次調用,不會再次構造std::cout << "main()結束" << std::endl;return 0;
}

輸出結果:

main()開始
MyClass構造函數執行
func()被調用
func()被調用
main()結束
MyClass析構函數執行

如果注釋掉main()中對func()的所有調用,那么obj的構造函數永遠不會執行。

這種特性使得局部靜態變量非常適合實現"懶漢式"單例模式,確保對象在第一次使用時才被創建。

2. 與全局變量對比

在C++中,全局變量和局部靜態變量雖然都存儲在靜態存儲區(生命周期貫穿整個程序),但它們的初始化時機存在顯著差異,主要體現在初始化的觸發條件和具體階段上。以下是詳細說明:

1. 全局變量的初始化時機

全局變量(包括全局作用域的變量、命名空間作用域的變量、類的靜態成員變量)的初始化發生在程序執行之前,具體分為兩個階段:

(1)靜態初始化階段(編譯期/加載期)
  • 對于內置類型(如intdouble)或常量表達式初始化的變量,編譯器在編譯期即可確定初始值,直接將值寫入目標文件的靜態存儲區。
    例如:int g_a = 10;(常量初始化)、int g_b;(零初始化,默認值為0)。
(2)動態初始化階段(運行期,main函數執行前)
  • 對于需要運行時計算初始值的全局變量(如自定義類的對象、依賴其他變量的表達式),初始化發生在main函數執行之前,由程序的啟動代碼(CRT初始化代碼)觸發。
    例如:
    class MyClass { public: MyClass() { /* 構造函數 */ } };
    MyClass g_obj; // 動態初始化,構造函數在main前執行
    int g_c = g_a + 5; // 依賴其他變量,動態初始化
    


int g_c = g_a + 5;
這句話下斷點是可以卡住的

核心特點

  • 全局變量的初始化不依賴任何函數調用,無論是否被使用,都會在程序啟動階段完成初始化。
  • 所有全局變量的初始化在main函數執行前全部完成。

2. 局部靜態變量的初始化時機

局部靜態變量(函數內部定義的static變量)的初始化時機與全局變量完全不同:

(1)初始化觸發條件

局部靜態變量的初始化僅在函數第一次被調用時觸發,且只初始化一次。
例如:

void func() {static MyClass obj; // 僅在func第一次被調用時初始化static int num = 100; // 僅在func第一次被調用時賦值
}
(2)初始化階段(運行期,函數首次調用時)
  • 對于內置類型,第一次進入函數時執行初始化(如static int num = 100;)。
  • 對于自定義類的靜態局部對象,其構造函數在函數第一次被調用時執行
  • 如果函數從未被調用,局部靜態變量不會被初始化(構造函數也不會執行)。

核心特點

  • 初始化依賴函數的首次調用,是“按需初始化”。
  • C++11及以后標準保證局部靜態變量的初始化是線程安全的(多線程下首次調用不會導致重復初始化)。

3. 關鍵差異對比

特性全局變量局部靜態變量
初始化觸發條件程序啟動時(main前),不依賴函數調用函數第一次被調用時,依賴函數調用
初始化次數僅一次(程序啟動階段)僅一次(首次調用時)
未使用時是否初始化會初始化(無論是否被使用)不會初始化(函數未調用則不初始化)
構造函數執行時機main函數執行前函數第一次被調用時

4. 示例驗證

#include <iostream>class Test {
public:Test(const char* name) : m_name(name) {std::cout << m_name << " 構造函數執行" << std::endl;}~Test() {std::cout << m_name << " 析構函數執行" << std::endl;}
private:const char* m_name;
};// 全局變量
Test g_test("全局變量");void func() {// 局部靜態變量static Test l_test("局部靜態變量");std::cout << "func() 被調用" << std::endl;
}int main() {std::cout << "main() 開始" << std::endl;func(); // 首次調用func,觸發局部靜態變量初始化func(); // 再次調用,不觸發初始化std::cout << "main() 結束" << std::endl;return 0;
}

輸出結果

全局變量 構造函數執行  // 全局變量在main前初始化
main() 開始
局部靜態變量 構造函數執行  // 首次調用func時初始化
func() 被調用
func() 被調用
main() 結束
局部靜態變量 析構函數執行  // 程序結束時銷毀
全局變量 析構函數執行      // 程序結束時銷毀

結論

  • 全局變量的構造函數在main開始前執行,局部靜態變量的構造函數在函數首次調用時執行。
  • 若注釋掉main中對func()的調用,局部靜態變量的構造函數不會執行,而全局變量的構造函數仍會執行。

總結

全局變量和局部靜態變量的核心差異在于初始化是否依賴函數調用

  • 全局變量是“主動初始化”,程序啟動即完成,與是否被使用無關;
  • 局部靜態變量是“被動初始化”,僅在函數首次調用時觸發,實現了“懶加載”特性,這也是其常用于單例模式的原因。

3.同為靜態存儲區為什么初始化時機不同

靜態變量存放在靜態存儲區,程序執行前會初始化,但局部靜態變量為什么沒有在執行前初始化,而全局變量會先初始化。首先要明確靜態存儲區的概念,確實全局變量、靜態全局變量、局部靜態變量都存放在靜態存儲區,但它們的初始化時機不同。

首先,全局變量(包括靜態全局變量)屬于靜態存儲期,它們的初始化發生在程序啟動的靜態初始化階段(在 main 函數執行之前)。對于自定義類的全局對象,其構造函數會在這個階段執行,也就是 main 之前。

而局部靜態變量雖然也存放在靜態存儲區,但其初始化屬于延遲初始化(lazy initialization),這是 C++ 標準明確規定的:局部靜態變量在函數第一次被調用時才會初始化,而非程序啟動時。這是為了避免不必要的初始化開銷 —— 如果函數從未被調用,局部靜態變量就不會占用初始化資源。

總結核心區別:

存儲區域:全局變量和局部靜態變量都在靜態存儲區(生命周期都是程序整個運行期間)。
初始化時機:
全局變量:在 main 函數執行前的靜態初始化階段完成(構造函數此時調用)。
局部靜態變量:在函數第一次被調用時才初始化(構造函數此時調用)。
未使用情況:
全局變量:無論是否被使用,都會在程序啟動時初始化(構造函數一定會執行)。
局部靜態變量:如果函數從未被調用,不會初始化(構造函數永不執行)。

雖然同屬靜態存儲區,但全局變量和局部靜態變量的初始化時機存在差異 ——C++ 標準對它們的初始化策略做了不同設計,以平衡效率和確定性。


后續

全局變量(包括全局作用域的變量、命名空間作用域的變量、類的靜態成員變量)的初始化發生在程序執行之前

    1. 看下類的靜態變量與全局變量性質是否一直
    1. 全局變量與靜態全局變量的區別,即static對全局變量作用域影響詳解

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96644.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96644.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96644.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

水體反光 + 遮擋難題破解!陌訊多模態融合算法在智慧水務的實測優化

一、智慧水務行業檢測痛點&#xff08;數據支撐 場景難點&#xff09; 根據《2023 年中國智慧水務發展報告》&#xff0c;當前水務監控系統在核心業務場景中面臨兩大效率瓶頸&#xff0c;直接影響水廠運維與供水安全&#xff1a; 高誤報率導致運維資源浪費&#xff1a;水廠沉…

C++的指針和引用:

目錄 引用&#xff1a; 注意&#xff1a; 左值引用和右值引用&#xff1a; 左值引用&#xff1a; 右值引用&#xff1a; 指針&#xff1a; 指針與引用的區別&#xff1a; 引用&#xff1a; 在C中&#xff0c;?引用?是一種為已存在變量創建別名的機制&#xff0c;它允…

圖像處理中的偽影

目錄 一、塊效應偽影 / 塊狀偽影 二、 去除塊狀偽影 三、振鈴偽影 一、塊效應偽影 / 塊狀偽影 塊狀偽影(Blocking Artefacts)是對經過變換編碼的圖像進行重建時&#xff0c;圖像中可能會出現壓縮過程產生的可見偽影。基于塊的變換編碼中&#xff0c;一種常見偽影是 “塊效應…

Java:對象的淺拷貝與深拷貝

目錄 一、概念 二、實現方式 2.1 淺拷貝&#xff08;不推薦&#xff09; 2.2 深拷貝 2.2.1 方法一&#xff1a;重寫 clone() 方法并遞歸克隆&#xff08;常用&#xff09; 2.2.2 方法二&#xff1a;通過序列化實現&#xff08;更強大&#xff0c;但更重&#xff09; 2.2…

佰鈞成 社招 一面

1. “評估需求、排期”的工作流程&#xff1f; “我的工作流程一般是這樣的&#xff1a; 需求評審&#xff1a; 首先會和產品、后端同學一起過需求&#xff0c;確保我完全理解了業務背景和要實現的價值&#xff0c;而不僅僅是功能點。技術方案設計&#xff1a; 之后&#xff0c…

最短路徑問題(圖論)

1 Floyd 作用&#xff1a; 求圖中所有頂點之間的最短路徑&#xff0c;包括有向圖或者無向圖&#xff0c;權重正負皆可&#xff0c;用來一次性求所有點之間的最短路徑。 思路是 通過逐步擴大中間層&#xff0c;使得最短路徑不斷被更新&#xff0c;直到中間層擴大到n位置&#…

2025年8月新算法—云漂移優化算法(Cloud Drift Optimization Algorithm, CDO)

1、簡介 這項研究介紹了云漂移優化&#xff08;數位長&#xff09;算法&#xff0c;這是一種創新的自然啟發的元啟發式方法來解決復雜的優化問題。CDO模仿受大氣力影響的云粒子的動態行為&#xff0c;在探索和利用之間取得了微妙的平衡。它具有自適應權重調整機制&#xff0c;可…

VS Code進行.NET開發時使用斷點和熱重載

VS Code 調試熱重載 1. VS Code 設置 安裝擴展&#xff1a;C#、C# Dev Kit設置中搜索hot reload&#xff0c;選擇C#開發工具包&#xff0c;把下圖的幾項全部打勾2. 啟動項目&#xff08;僅用左側“運行和調試”&#xff09; 打開解決方案&#xff0c;選你的啟動項目的“.NET La…

mysqlbinlog解析命令

解析 MySQL Binlog 詳細信息的命令以下是解析 MySQL Binlog 詳細信息的常用命令&#xff1a;1. 基本 binlog 解析命令# 查看 binlog 文件內容&#xff08;基本格式&#xff09; mysqlbinlog /var/lib/mysql/mysql-bin.000001# 查看特定時間段的 binlog mysqlbinlog --start-dat…

算法訓練營day60 圖論⑩ Bellman_ford 隊列優化算法、判斷負權回路、單源有限最短路(修改后版本)

增加對最短路徑的優化算法、負權回路、單源有限最短的講解 Bellman_ford 隊列優化算法 -------------------------------------------------------------------------------- 8.24更新&#xff1a;該算法是針對帶負值的最短路徑的優化算法&#xff0c;核心通過隊列來實現&…

Python 學習(十六) 下一代 Python 包管理工具:UV

目錄1. UV 介紹1.1 什么是UV&#xff1f;1.2 UV的核心優勢1.3 UV和其他工具對比1&#xff09;UV vs. pipvirtualenv2&#xff09;UV vs. Conda3&#xff09;UV vs. Poetry4&#xff09;功能對比表2. UV的安裝與常用命令2.1 安裝UV1&#xff09;使用官方安裝腳本&#xff08;推薦…

Redis學習筆記 ----- 緩存

一、什么是緩存 緩存&#xff08;Cache&#xff09;是數據交換的緩沖區&#xff0c;是存儲數據的臨時地方&#xff0c;一般讀寫性能較高。 &#xff08;一&#xff09;緩存的作用 降低后端負載&#xff1a;減少對數據庫等后端存儲的直接訪問壓力。提高讀寫效率&#xff0c;降低…

React響應式鏈路

文章目錄響應式鏈路的核心環節1.狀態定義與初始化2.狀態更新觸發&#xff08;狀態變更&#xff09;3.調度更新&#xff08;Scheduler&#xff09;4.重新渲染&#xff08;Render 階段&#xff09;5.協調&#xff08;Reconciliation&#xff09;與 Fiber 架構6.提交更新&#xff…

軟件設計師——計算機網絡學習筆記

一、計算機網絡 網絡基礎 1. 計算機網絡的分類2. 網絡拓撲結構 總線型(利用率低、干擾大、價格低) 星型(交換機形成的局域網、中央單元負荷大) 環型(流動方向固定、效率低擴充難) 樹型(總線型的擴充、分級結構) 分布式(任意節點連接、管理難成本高)一般來說&#xff0c;辦公室局…

1200 SCL學習筆記

一. IF. 如果。下面是一個起保停IF #I_start AND NOT #I_stop THEN //如果I_start接通 和 I_stop沒有接通#Q_run : 1; //輸出Q_run 接通 ELSIF #I_stop THEN //如果I_stop接通#Q_run : 0; //。。。。。。 END_IF;二. CASECASE…

單例模式與線程池

1. 單例模式單例模式是一種常用的設計模式&#xff0c;它確保一個類只有一個實例&#xff0c;并提供一個全局訪問點來獲取這個實例。這種模式在需要控制資源訪問、管理共享狀態或協調系統行為時非常有用。單例模式的核心特點&#xff1a;私有構造函數&#xff1a;防止外部通過n…

Chrome和Edge如何開啟暗黑模式

Edge和Chrome瀏覽器都提供了實驗性功能&#xff0c;可以通過修改實驗性設置來開啟暗黑模式。 在瀏覽器地址欄中輸入edge://flags/&#xff08;Edge&#xff09;或chrome://flags/&#xff08;Chrome&#xff09;。在搜索框中輸入“dark”&#xff0c;找到與暗黑模式相關的選項。…

【科研繪圖系列】浮游植物的溶解性有機碳與初級生產力的關系

禁止商業或二改轉載,僅供自學使用,侵權必究,如需截取部分內容請后臺聯系作者! 文章目錄 介紹 數據準備 數據處理 溶解性有機碳(DOC)與初級生產力(NPP)的關系 溶解性有機碳(DOC)與光照強度(PAR)的關系 數據可視化 加載R包 數據下載 導入數據 畫圖1 畫圖2 總結 系統信…

IDEA相關的設置和技巧

IDEA相關的設置和技巧 我的博客對應文章地址 1.布局設置 IDEA的布局自定義程度很高&#xff0c;頂部工具欄&#xff0c;側邊欄都可以隨意定制&#xff0c;設置好的布局方案可以保存&#xff0c;在新項目中快速使用 1.1 工具欄設置 [!tip] 舉個例子&#xff1a;比如我要在頂部…

AWS Lambda 完全指南:解鎖無服務器架構的強大力量

在云計算的發展浪潮中,無服務器(Serverless) 架構已然成為構建現代應用的新范式。而在這場變革的中心,AWS Lambda 作為開創性的 Function-as-a-Service (FaaS) 服務,徹底改變了我們部署和運行代碼的方式。 本文將帶您深入探索 AWS Lambda,從核心概念、工作原理到高級實踐…