一、C++ 基礎
1.1 語言特性與區別
-
C++ 與 C 的主要區別是什么?C++ 為何被稱為 “帶類的 C”?
- 主要區別:C++ 引入了面向對象編程(OOP)特性(類、繼承、多態等),而 C 是過程式編程語言;C++ 支持函數重載、模板、異常處理等高級特性,C 不支持。
- 為何稱為"帶類的 C":早期 C++ 是在 C 語言基礎上添加類和對象機制擴展而來,保留了 C 的大部分語法和功能,因此被稱為"帶類的 C"。
-
C++ 的基本數據類型有哪些?它們的大小(32 位 / 64 位系統)分別是多少?
- 基本數據類型:整數類型(char、short、int、long、long long)、浮點類型(float、double、long double)、布爾類型(bool)、空類型(void)。
- 大小(32位/64位系統):
- char:1字節
- short:2字節
- int:4字節
- long:4字節(32位)/ 8字節(64位)
- long long:8字節
- float:4字節
- double:8字節
- bool:1字節
-
指針和引用的區別是什么?何時使用指針,何時使用引用?
-
區別:
- 指針可以為空(nullptr),引用必須初始化且不能為null;
- 指針可以重新指向其他對象,引用一旦綁定不能更改;
- 指針需要解引用(*)訪問對象,引用直接訪問;
- 指針有自己的內存空間,引用不占用額外內存(編譯器處理為指針)。
-
使用場景:
- 當需要表示"無對象"時使用指針(如返回可能失敗的查找結果);
- 當需要動態修改指向的對象時使用指針;
- 其他情況優先使用引用(更安全、簡潔)。
-
代碼示例:
int a = 10; int* p = &a; // 指針 int& r = a; // 引用 *p = 20; // 解引用修改a的值 r = 30; // 直接修改a的值 p = nullptr; // 指針可以為空 // r = nullptr; // 錯誤:引用不能為null
-
-
結構體 struct 和共同體 union(聯合)的區別是什么?
-
struct:各成員占用獨立內存空間,總大小為各成員大小之和(考慮內存對齊)。
-
union:所有成員共享同一塊內存空間,總大小為最大成員的大小。同一時間只能有一個成員有效。
-
代碼示例:
struct S { int a; char b; }; // 大小至少為 4+1=5字節(實際可能更大,取決于對齊) union U { int a; char b; }; // 大小為4字節(int的大小)
-
-
struct 和 class 的區別?
- 默認訪問權限:struct默認public,class默認private。
- 默認繼承方式:struct默認public繼承,class默認private繼承。
- 模板參數:class可用于定義模板參數(如template),struct不行(C++11后無區別)。
-
C++ 是不是類型安全的?為什么?
- 不是完全類型安全。原因:
- 存在強制類型轉換(如reinterpret_cast可以任意轉換指針類型);
- 指針運算可能導致越界訪問;
- 空指針解引用;
- 未初始化的變量;
- 數組與指針的隱式轉換。
- 不是完全類型安全。原因:
1.2 關鍵字與修飾符
-
const 關鍵字的用法(修飾變量、函數參數、函數返回值、成員函數)?
-
修飾變量:表示變量的值不能被修改。
const int MAX = 100; // 常量
-
修飾函數參數:防止函數修改實參的值。
void print(const std::string& message); // 引用傳遞,不修改原字符串
-
修飾函數返回值:表示返回值不能被修改(通常用于返回指針或引用時)。
const char* getName() const; // 返回常量指針
-
修飾成員函數:表示該函數不會修改類的成員變量。
class Test { public:int getValue() const; // 不會修改成員變量 };
-
-
volatile 關鍵字的作用?它與 const 能否同時修飾一個變量?
-
作用:告訴編譯器不要對該變量進行優化,每次都從內存中讀取值。通常用于多線程環境中共享的變量或硬件寄存器映射的變量。
-
與const共存:可以同時修飾一個變量,表示變量的值不能被程序修改,但可能被其他因素(如硬件、其他線程)修改。
volatile const int* p = &someRegister; // 指向只讀寄存器的指針
-
-
typedef 和 using(C++11)的區別?如何用它們定義函數指針?
-
區別:
- typedef是C語言的關鍵字,using是C++11引入的別名聲明;
- using可以定義模板別名,typedef不能;
- using的語法更清晰,尤其是在復雜類型別名時。
-
定義函數指針:
// 使用typedef typedef int (*FuncPtr)(int, int);// 使用using(C++11) using FuncPtr = int (*)(int, int);// 使用示例 int add(int a, int b) { return a + b; } FuncPtr f = add;
-
-
typedef 和 #define 的區別是什么?
- 類型檢查:typedef會進行類型檢查,#define只是簡單的文本替換;
- 作用域:typedef有作用域限制,#define全局有效;
- 復雜性:typedef適合定義復雜類型(如函數指針),#define更適合簡單的文本替換;
- 調試:typedef在調試時更友好,可以顯示類型信息。
-
extern “C” 的作用是什么?
- 作用:指定使用C語言的函數命名規則和調用約定,以便C++代碼能夠調用C語言編寫的函數。
- 使用場景:在C++項目中調用C語言庫,或者讓C項目調用C++編寫的接口。
// 在C++代碼中聲明C函數
extern "C" {void cFunction();
}// 在C++頭文件中同時支持C和C++編譯
#ifdef __cplusplus
extern "C" {
#endifvoid commonFunction();
#ifdef __cplusplus
}
#endif
-
static 關鍵字的作用有哪些(函數體內、模塊內、類中)?
-
函數體內:變量在函數調用之間保持值,只初始化一次。
void increment() {static int count = 0; // 只初始化一次count++; // 每次調用都增加 }
-
模塊內:限制變量或函數的作用域為本模塊(.cpp文件),不能被其他模塊訪問。
static void helper() { /* 只在當前文件可見 */ }
-
類中:靜態成員變量屬于類而不是對象,靜態成員函數不依賴于對象實例。
class MyClass { public:static int count; // 靜態成員變量聲明static void printCount() { // 靜態成員函數std::cout << count << std::endl;} }; int MyClass::count = 0; // 靜態成員變量定義
-
-
auto(C++11 前僅用于自動變量)和 decltype 的區別?
-
auto:根據初始化表達式自動推導變量類型,需要初始化。
auto x = 10; // x被推導為int類型 auto s = "hello"; // s被推導為const char*
-
decltype:根據表達式推導類型,不需要初始化。
int a = 5; decltype(a) b; // b被推導為int類型 decltype(a + 1.0) c; // c被推導為double類型
-
-
nullptr 與 NULL 的區別?為何推薦使用 nullptr?
-
NULL:通常是一個宏定義,在C++中被定義為0或 (void*)0,可能導致類型歧義。
-
nullptr:C++11引入的關鍵字,表示空指針常量,類型為nullptr_t。
-
推薦使用nullptr的原因:
- 避免整數和指針類型混淆;
- 在函數重載時提供明確的類型信息;
- 提高代碼的可讀性和安全性。
void f(int); void f(int*); f(NULL); // 可能調用f(int)而不是f(int*) f(nullptr); // 明確調用f(int*)
-
1.3 內存管理
-
C++ 的內存分區(棧、堆、全局 / 靜態存儲區、常量存儲區、代碼區)各自的特點?
- 棧(Stack):
- 由編譯器自動分配釋放;
- 存儲函數參數、局部變量等;
- 空間小,訪問速度快;
- 內存分配是連續的。
- 堆(Heap):
- 由程序員動態分配和釋放;
- 空間較大,訪問速度相對較慢;
- 內存分配不一定連續,可能產生碎片。
- 全局/靜態存儲區:
- 存儲全局變量和靜態變量;
- 程序運行期間一直存在;
- 程序結束時由系統釋放。
- 常量存儲區:
- 存儲常量(如字符串常量、const常量);
- 內容不允許修改。
- 代碼區:
- 存儲程序的二進制指令;
- 通常是只讀的。
- 棧(Stack):
-
堆和棧的區別(申請方式、大小限制、生命周期、碎片問題等)?
- 申請方式:棧由編譯器自動分配;堆由程序員調用malloc/free或new/delete分配。
- 大小限制:棧的大小通常較小(幾MB);堆的大小受限于系統虛擬內存(幾GB)。
- 生命周期:棧中的數據在函數調用結束后自動銷毀;堆中的數據需要手動釋放,否則會造成內存泄漏。
- 碎片問題:頻繁分配/釋放堆內存會產生碎片;棧不會產生碎片。
- 分配效率:棧的分配效率高;堆的分配效率相對較低。
-
什么是內存泄漏?如何檢測和避免內存泄漏?
- 內存泄漏:程序中動態分配的內存沒有被正確釋放,導致這部分內存無法被重用。
- 檢測方法:
- 使用內存檢測工具(如Valgrind、Visual Leak Detector);
- 在代碼中添加內存跟蹤日志;
- 使用智能指針自動管理內存。
- 避免方法:
- 及時釋放動態分配的內存;
- 使用RAII原則(Resource Acquisition Is Initialization);
- 優先使用智能指針(如std::unique_ptr、std::shared_ptr);
- 避免復雜的內存管理邏輯。
-
C 和 C++ 動態管理內存的方法有何不同(malloc/free vs new/delete)?
-
malloc/free:C語言的函數,只負責分配/釋放內存空間,不調用構造函數/析構函數。
int* p = (int*)malloc(sizeof(int)); free(p);
-
new/delete:C++的運算符,不僅分配/釋放內存,還會調用構造函數/析構函數。
int* p = new int(10); // 分配內存并初始化 delete p; // 調用析構函數并釋放內存// 數組版本 int* arr = new int[5]; delete[] arr; // 必須使用delete[]釋放數組
-
其他區別:
- new/delete是運算符,malloc/free是函數;
- new自動計算所需內存大小,malloc需要手動計算;
- new分配失敗時拋出異常,malloc返回nullptr;
- new/delete可以重載,malloc/free不能。
-
-
new 和 delete 是如何實現的?與 malloc 和 free 有何異同?
- new的實現:
- 調用operator new分配內存;
- 調用構造函數初始化對象。
- delete的實現:
- 調用析構函數清理對象;
- 調用operator delete釋放內存。
- 與malloc/free的異同:
- 相同點:都是用于動態內存管理。
- 不同點:
- new/delete是運算符,malloc/free是函數;
- new/delete會調用構造函數/析構函數,malloc/free不會;
- new自動計算內存大小,malloc需要手動指定;
- new失敗時拋出異常,malloc返回nullptr;
- new/delete可以重載,malloc/free不能。
- new的實現:
-
delete 和 delete [] 的區別是什么?
- delete:用于釋放單個對象的內存,先調用該對象的析構函數,然后釋放內存。
- delete []:用于釋放數組對象的內存,先調用數組中每個對象的析構函數,然后釋放內存。
- 混用的后果:用delete釋放數組會導致只調用第一個對象的析構函數,造成內存泄漏;用delete []釋放單個對象會導致多次調用析構函數,可能導致程序崩潰。
-
什么是野指針?其成因有哪些?
- 野指針:指向已釋放內存或未分配內存的指針,無法確定其指向的內容。
- 成因:
- 指針未初始化;
- 指針指向的內存已被釋放,但指針未置空;
- 指針越界訪問。
- 避免方法:
- 指針初始化時置為nullptr;
- 釋放內存后將指針置為nullptr;
- 使用智能指針代替裸指針。
-
棧溢出的原因是什么?有哪些解決方法?
- 原因:
- 函數調用層次過深(如遞歸調用沒有終止條件);
- 局部變量占用過多棧空間(如大型數組);
- 棧空間設置過小。
- 解決方法:
- 優化遞歸算法,避免過深的調用層次;
- 使用堆內存代替棧內存存儲大型數據;
- 調整棧空間大小(特定編譯器/環境下)。
- 原因:
-
C++ 的內存管理中,自由存儲區與堆的區別是什么?
- 自由存儲區:由C++的new/delete運算符分配和釋放的內存區域,是C++概念。
- 堆:由C語言的malloc/free函數分配和釋放的內存區域,是操作系統概念。
- 關系:通常情況下,自由存儲區和堆是同一塊內存區域,但嚴格來說,自由存儲區是基于堆實現的。
- 區別:
- 分配/釋放方式不同:自由存儲區使用new/delete,堆使用malloc/free;
- 內存管理機制不同:new/delete會調用構造函數/析構函數,malloc/free不會;
- new/delete可以重載,改變自由存儲區的分配策略。
1.4 函數與預處理
-
函數重載的原理是什么?為何返回值不同不能構成重載?
-
原理:編譯器根據函數的參數類型、數量、順序生成不同的函數名(名稱修飾),使得同名但參數列表不同的函數在編譯后實際上有不同的標識符。
-
返回值不同不能構成重載:因為在函數調用時,編譯器無法僅根據返回值類型來確定要調用哪個函數。函數調用的語法中,返回值是可選的,不影響函數的選擇。
void print(int); void print(double); // 重載,參數類型不同 // int print(int); // 錯誤:僅返回值不同,不能構成重載
-
-
函數默認參數的規則(從右向左設置,調用時不能跳過前面的參數)?
-
從右向左設置:默認參數必須從最右邊的參數開始設置,不能跳過中間的參數。
void func(int a, int b = 10, int c = 20); // 正確 // void func(int a = 10, int b, int c = 20); // 錯誤:不能跳過b
-
調用時不能跳過前面的參數:在調用有默認參數的函數時,必須從左到右提供參數,不能跳過前面的參數而直接使用后面的默認參數。
func(5); // 等同于func(5, 10, 20) func(5, 15); // 等同于func(5, 15, 20) // func(, 15); // 錯誤:不能跳過前面的參數
-
-
內聯函數(inline)的作用?與宏定義的區別?為何內聯函數不宜過長?
-
作用:減少函數調用的開銷,提高程序運行效率。編譯器會嘗試將內聯函數的代碼插入到調用點,而不是進行函數調用。
-
與宏定義的區別:
- 內聯函數由編譯器處理,進行類型檢查;宏由預處理器處理,僅進行文本替換。
- 內聯函數支持調試;宏不支持調試。
- 內聯函數更安全,不會有宏替換可能帶來的副作用;宏可能因優先級問題導致錯誤。
// 內聯函數 inline int max(int a, int b) { return a > b ? a : b; }// 宏定義 #define MAX(a, b) ((a) > (b) ? (a) : (b))
-
內聯函數不宜過長的原因:
- 如果內聯函數過長,編譯器可能會忽略inline關鍵字,將其當作普通函數處理;
- 過長的內聯函數會導致代碼膨脹,增加可執行文件的大小。
-
-
預處理指令(#include、#define、#ifdef 等)的作用?#include <> 和 #include “” 的區別?
- 常用預處理指令:
#include
:包含頭文件;#define
:定義宏;#undef
:取消宏定義;#ifdef
/#ifndef
:條件編譯(如果宏已定義/未定義);#else
/#elif
:條件編譯的分支;#endif
:結束條件編譯塊;#pragma
:特定編譯器的指令。
- #include <> 和 #include “” 的區別:
#include <>
:用于包含標準庫頭文件,編譯器會先在標準庫目錄中查找;#include ""
:用于包含用戶自定義頭文件,編譯器會先在當前目錄中查找,然后再到標準庫目錄中查找。
- 常用預處理指令:
-
定義和聲明的區別是什么?
-
聲明(Declaration):告訴編譯器某個名字的存在及其類型,但不分配內存或初始化。
extern int g_var; // 變量聲明 int func(int a); // 函數聲明
-
定義(Definition):聲明的同時分配內存或提供函數體。一個變量或函數可以有多個聲明,但只能有一個定義。
int g_var = 10; // 變量定義 int func(int a) { return a * 2; } // 函數定義
-
-
引用作為函數參數以及返回值的好處是什么?有哪些限制?
-
作為函數參數的好處:
- 避免拷貝大對象,提高效率;
- 可以修改實參的值(如果不是const引用);
- 比指針更安全,不需要檢查空指針。
-
作為函數返回值的好處:
- 避免返回值的拷貝,提高效率;
- 可以直接修改返回的對象(鏈式調用)。
-
限制:
- 引用必須綁定到有效的對象,不能返回局部變量的引用;
- 返回引用時需要確保引用的對象在函數調用結束后仍然存在。
// 正確:返回類成員的引用 class MyClass { private:int value; public:int& getValue() { return value; } };// 錯誤:返回局部變量的引用 int& badFunction() {int x = 10;return x; // x在函數結束后被銷毀 }
-
-
C++ 文件編譯與執行的四個階段是什么?
- 預處理(Preprocessing):
- 處理預處理指令(如#include、#define);
- 刪除注釋;
- 宏展開;
- 生成.i文件。
- 編譯(Compilation):
- 將預處理后的代碼轉換為匯編語言;
- 進行語法檢查、語義分析、優化等;
- 生成.s文件。
- 匯編(Assembly):
- 將匯編代碼轉換為機器碼(二進制);
- 生成.obj文件(Windows)或.o文件(Unix/Linux)。
- 鏈接(Linking):
- 將多個目標文件和庫文件鏈接在一起;
- 解決符號引用;
- 生成可執行文件(.exe文件或ELF文件)。
- 預處理(Preprocessing):
-
頭文件中 #ifndef/define/endif 與 #pragma once 的區別是什么?
-
#ifndef/define/endif(條件包含):
- 標準C++語法,所有編譯器都支持;
- 可以嵌套使用,實現更復雜的條件包含;
- 可能存在宏名沖突的問題。
#ifndef HEADER_FILE_NAME_H #define HEADER_FILE_NAME_H// 頭文件內容#endif // HEADER_FILE_NAME_H
-
#pragma once:
- 非標準但被大多數編譯器支持的語法;
- 更簡潔,不容易出錯;
- 依賴于編譯器對文件的識別,可能在某些情況下(如硬鏈接)導致重復包含。
#pragma once// 頭文件內容
-
選擇建議:兩種方式都可以使用,通常推薦使用
#pragma once
,因為它更簡潔且不容易出錯,但如果需要兼容不支持#pragma once
的編譯器,或者需要更復雜的條件包含,可以使用#ifndef/define/endif
。
-
1.5 類型轉換與其他
-
C++ 的四種強制轉換(static_cast、dynamic_cast、const_cast、reinterpret_cast)分別是什么?各自的適用場景?
-
static_cast:
- 適用場景:用于基本類型轉換、上行轉換(派生類指針/引用轉為基類指針/引用)、轉換編譯器能明確識別的類型。
- 特點:編譯時進行檢查,不進行運行時類型檢查。
int i = 10; double d = static_cast<double>(i); // 基本類型轉換Base* base = static_cast<Base*>(derived); // 上行轉換(安全)
-
dynamic_cast:
- 適用場景:主要用于下行轉換(基類指針/引用轉為派生類指針/引用),必須用于包含虛函數的類。
- 特點:運行時進行類型檢查,如果轉換失敗返回nullptr(指針)或拋出異常(引用)。
Base* base = new Derived(); Derived* derived = dynamic_cast<Derived*>(base); // 下行轉換,需要運行時檢查
-
const_cast:
- 適用場景:用于移除變量的const或volatile限定符。
- 注意:如果原對象本身不是const的,使用const_cast修改是合法的;如果原對象是const的,修改其內容是未定義行為。
const int* p = &value; int* q = const_cast<int*>(p); // 移除const限定符
-
reinterpret_cast:
- 適用場景:用于不同類型指針之間的轉換、指針與整數之間的轉換,是最不安全的轉換方式。
- 特點:僅重新解釋指針的二進制表示,不進行任何類型檢查或轉換。
int* p = reinterpret_cast<int*>(0x12345678); // 指針與整數轉換
-
-
左值和右值的區別?左值引用與右值引用的區別是什么?
- 左值(lvalue):表達式結束后依然存在的持久對象,可以出現在賦值語句的左側。
- 右值(rvalue):表達式結束后就不再存在的臨時對象,只能出現在賦值語句的右側。
int a = 10; // a是左值,10是右值 int& lr = a; // 左值引用綁定到左值 // int& lr2 = 10; // 錯誤:左值引用不能綁定到右值 const int& lr3 = 10; // 常量左值引用可以綁定到右值// C++11引入的右值引用 int&& rr = 10; // 右值引用綁定到右值 // int&& rr2 = a; // 錯誤:右值引用不能直接綁定到左值 int&& rr3 = std::move(a); // 使用std::move將左值轉換為右值
- 左值引用(&):
- 可以綁定到左值或const右值;
- 通常用于函數參數傳遞,避免拷貝。
- 右值引用(&&):
- C++11引入,用于實現移動語義和完美轉發;
- 只能綁定到右值或通過std::move轉換的左值;
- 主要用于移動構造函數、移動賦值運算符和完美轉發。
-
指針數組和數組指針的區別是什么?
-
指針數組:一個數組,其元素是指針。
int* arr[5]; // 包含5個int*指針的數組
-
數組指針:一個指針,指向一個數組。
int (*ptr)[5]; // 指向包含5個int元素的數組的指針 int arr[5]; ptr = &arr; // 正確:ptr指向整個數組
-
記憶方法:看括號的位置,
*
和變量名在括號內的是數組指針,否則是指針數組。
-
-
sizeof 和 strlen 的區別是什么?
-
sizeof:
- 運算符,不是函數;
- 編譯時計算;
- 計算變量、類型或表達式所占的字節數;
- 對于數組名,計算整個數組的大小;
- 對于指針,計算指針本身的大小(通常為4或8字節)。
-
strlen:
- 函數,定義在頭文件中;
- 運行時計算;
- 計算字符串的長度,不包括結束符’\0’;
- 只適用于以’\0’結尾的字符數組(C風格字符串)。
char str[] = "hello"; sizeof(str); // 6字節(包含'\0') strlen(str); // 5字節(不包含'\0')char* p = str; sizeof(p); // 4或8字節(指針的大小) strlen(p); // 5字節
-
-
關于 sizeof 的詳細小結(包括不同類型、數組、結構體等)?
-
基本類型:sizeof(char) = 1字節,sizeof(short) = 2字節,sizeof(int) = 4字節,sizeof(long) = 4或8字節,sizeof(long long) = 8字節,sizeof(float) = 4字節,sizeof(double) = 8字節,sizeof(bool) = 1字節。
-
指針類型:在32位系統上,sizeof(任意指針) = 4字節;在64位系統上,sizeof(任意指針) = 8字節。
-
數組:sizeof(數組名) = 數組元素個數 * sizeof(元素類型),但當數組名作為函數參數傳遞時,會退化為指針,sizeof(指針) = 指針本身的大小。
int arr[10]; sizeof(arr); // 10 * sizeof(int) = 40字節(假設int為4字節)void func(int a[]) {sizeof(a); // 4或8字節(指針的大小) }
-
結構體:sizeof(結構體)考慮內存對齊,通常大于或等于各成員大小之和。
struct S {char c; // 1字節int i; // 4字節double d; // 8字節 }; // 在大多數64位系統上,sizeof(S) = 16字節(內存對齊)
-
空類:C++中空類的大小為1字節(為了區分不同的對象實例)。
class Empty { }; sizeof(Empty); // 1字節
-
-
結構體為什么需要內存對齊?內存對齊的原因是什么?
- 內存對齊:編譯器為了提高訪問效率,會對結構體成員進行對齊,使每個成員的起始地址是其大小的整數倍。
- 內存對齊的原因:
- 性能考慮:處理器訪問對齊的內存比未對齊的內存更快;
- 硬件限制:某些處理器架構不支持非對齊的內存訪問,或者訪問非對齊內存會導致性能下降;
- 兼容性:不同編譯器、不同平臺可能有不同的對齊要求,適當的對齊可以保證數據結構的兼容性。
- 對齊規則:
- 每個成員的起始地址必須是其自身大小的整數倍;
- 整個結構體的大小必須是其最大成員大小的整數倍(或編譯器指定的最大對齊值的整數倍)。
-
引用是否占用內存空間?為什么?
- 從實現角度:引用通常在編譯器內部實現為指針,因此會占用內存空間(在32位系統上為4字節,在64位系統上為8字節)。
- 從語言標準角度:C++標準沒有明確規定引用是否占用內存空間,只是規定引用必須綁定到一個對象,并且不能重新綁定。
- 為何會有這樣的設計:引用的設計目的是提供一個便捷、安全的別名機制,而不是作為一種新的指針類型。因此,標準并不關心其具體實現細節,只關心其行為。
-
全局變量和局部變量的區別是什么?是怎么實現的?操作系統和編譯器是怎么知道的?
- 全局變量:
- 定義在函數外部,作用域為整個程序;
- 存儲在全局/靜態存儲區,程序啟動時分配內存,程序結束時釋放;
- 默認初始化為0或空指針。
- 局部變量:
- 定義在函數內部,作用域為函數內部或代碼塊內部;
- 存儲在棧區,函數調用時分配內存,函數返回時釋放;
- 不默認初始化,其值是未定義的。
- 實現方式:
- 編譯器在編譯時為全局變量和局部變量分配不同的內存區域;
- 全局變量的地址在編譯時或鏈接時確定;
- 局部變量的地址在運行時動態確定(基于棧指針)。
- 操作系統和編譯器如何知道:
- 編譯器在編譯過程中會記錄變量的作用域和存儲類型;
- 鏈接器會處理全局變量的引用;
- 操作系統在加載程序時,根據可執行文件中的段信息(如.data段、.bss段)來分配和初始化全局變量的內存空間。
- 全局變量:
-
main 函數執行之前,還會執行什么代碼?
-
全局變量的構造函數:程序啟動時,會先初始化全局變量和靜態變量,并調用它們的構造函數;
-
C++ 運行時庫初始化:初始化C++標準庫,如內存分配器、異常處理機制等;
-
main函數的參數設置:解析命令行參數,設置argc和argv;
-
其他初始化:如靜態對象的初始化、線程局部存儲的初始化等。
-
示例:
class MyClass { public:MyClass() { std::cout << "MyClass constructor called" << std::endl; } };MyClass globalObj; // 全局對象,其構造函數在main之前調用int main() {std::cout << "main function called" << std::endl;return 0; } // 輸出順序: // MyClass constructor called // main function called
-