alloca
是什么?
alloca 是一個非標準但廣泛支持的 C 語言函數,用于在當前函數的棧(stack)上動態分配內存。
- 與 malloc 的區別:
- malloc 在堆(heap) 上分配內存,需要手動調用 free 釋放。
- alloca 在棧上分配內存,函數返回時會自動釋放,無需手動 free。
- 優點:分配和釋放速度快(棧操作很快),內存自動管理。
- 缺點:
- 分配的內存大小受限于棧空間(通常比堆小得多),分配過大可能導致棧溢出(stack overflow)。
- 不是 C 標準的一部分,可移植性較差。
- 在循環中使用可能導致棧持續增長(雖然函數返回會釋放,但循環內反復調用仍可能有問題)。
__builtin_alloca
是什么?
__builtin_
開頭的函數是 GCC(GNU Compiler Collection)等編譯器提供的“內置函數”(built-in functions)。
- __builtin_alloca 是 GCC 對 alloca 功能的編譯器級實現。
- 它不是鏈接時從庫中加載的普通函數,而是在編譯階段由編譯器直接生成相應的匯編代碼(通常是調整棧指針 esp/rsp)。
- 這使得它比調用一個普通的庫函數 alloca 更高效,也更底層。
- 示例
#include <stdio.h>// 假設這個宏已經被定義(在某些系統頭文件中常見)
// #define alloca(size) __builtin_alloca(size)void example(int n) {// 在棧上分配 n 個 int 的空間int *arr = (int*)alloca(n * sizeof(int));// 使用分配的內存for (int i = 0; i < n; i++) {arr[i] = i * i;}printf("arr[5] = %d\n", arr[5]); // 假設 n > 5// 函數返回時,arr 所指向的棧內存自動釋放// 無需 free(arr)
}int main() {example(10);return 0;
}
在這個例子中,alloca(n * sizeof(int)) 會被預處理器替換為 __builtin_alloca(n * sizeof(int)),然后編譯器直接生成調整棧指針的指令來完成內存分配。
- 重要注意事項
- 不要在非葉函數(non-leaf function)中使用 alloca:如果函數 A 調用了 alloca,然后又調用了其他函數 B,B 的棧幀可能會覆蓋 A 中 alloca 分配的區域,導致未定義行為。
- 避免在循環中使用:雖然安全,但可能導致棧空間持續增長。
- 檢查返回值:alloca 和 __builtin_alloca 在分配失敗(如棧溢出)時不會返回 NULL,而是導致程序崩潰。因此無法像 malloc 那樣檢查錯誤。
- 可移植性問題:盡管廣泛支持,但在某些編譯器或系統上可能不可用。更現代、更安全的替代方案是使用 變長數組(VLA, Variable Length Array)(C99 標準支持,但 C11 不強制要求),例如:
void func(int n) {int arr[n]; // C99 VLA,在棧上分配// ...
} // arr 自動釋放
- Linux ManualPage中是這樣描述的
//在棧上分配內存,并在函數返回時自動釋放
#include <alloca.h>void *alloca(size_t size);
- 分配位置: 在調用者的棧幀(stack frame)中分配內存。
這意味著 alloca 分配的內存屬于調用它的那個函數的棧空間。
- 自動釋放: 當調用 alloca 的函數返回時,這塊內存會自動被回收(通過棧指針的移動)。
- 返回值:
- 成功時:返回指向分配內存的指針。
- 失敗時(棧溢出):未定義行為(undefined behavior)。
這是 alloca 最大的風險之一。它不會返回 NULL 來指示失敗。如果分配的內存過大,導致棧溢出,程序很可能直接崩潰(如段錯誤),且無法在代碼中安全地檢測和處理這種錯誤。
- 屬性:
- MT-Safe 表示該函數是線程安全的(Multi-Thread Safe)。
- 原因:每個線程都有自己的棧,alloca 在當前線程的棧上分配內存,不會與其他線程的棧發生沖突。因此,多個線程同時調用 alloca 是安全的。
- STANDARDS (標準)
None.
- 這是最關鍵的一點:alloca 不屬于任何正式的 C 語言標準(如 ISO C90, C99, C11, C17)。
- 它是一個非標準擴展,其存在和行為依賴于具體的編譯器和操作系統實現。
- 這意味著使用 alloca 的代碼可移植性較差,在某些編譯器或平臺上可能不可用。
- NOTES (注意事項)
- alloca() 函數的實現依賴于具體的機器架構和編譯器。因為它是在棧上進行分配,所以它比 malloc(3) 和 free(3) 更快。在某些情況下,對于使用 longjmp(3) 或 siglongjmp(3) 的應用程序,它也可以簡化內存釋放。除此之外,不鼓勵使用 alloca()。
- 由于 alloca() 分配的空間位于棧幀內,如果通過調用 longjmp(3) 或 siglongjmp(3) 跳過了函數的返回過程,那么這部分空間也會被自動釋放。
- 如果指向 alloca() 分配空間的指針只是簡單地超出了其作用域(例如,在嵌套代碼塊中定義的指針),那么該空間不會被自動釋放。
- 切勿嘗試使用 free(3) 來釋放 alloca() 分配的空間!
- 由于實現上的需要,alloca() 本質上是一個編譯器內置函數(built-in),也被稱為 __builtin_alloca()。現代編譯器默認會自動將所有對 alloca() 的調用轉換為該內置函數。但是,如果請求了標準符合性(如使用 -ansi 或 -std=c* 編譯選項),這種自動轉換是被禁止的,此時必須包含 <alloca.h> 頭文件,否則會產生一個符號依賴。
- alloca() 是一個內置函數這一事實意味著:無法獲取它的地址,也無法通過鏈接不同的庫來改變它的行為。
- 變長數組(Variable Length Arrays, VLAs) 是 C99 標準的一部分,自 C11 標準起變為可選特性,可以用于類似的目的。然而,它們無法移植到標準 C++ 中,并且作為變量,它們存在于其代碼塊的作用域內,沒有類似內存分配器的接口,因此不適合用于實現 strdupa(3) 這樣的功能。
- BUGS (缺陷)
- 由于棧的特性,無法檢查分配操作是否會超出棧的可用空間,因此也無法指示錯誤。(不過,如果程序試圖訪問不可用的空間,很可能會收到一個 SIGSEGV 信號。)
- 在許多系統上,不能在函數調用的參數列表中使用 alloca(),因為 alloca() 在棧上保留的空間會出現在函數參數所用空間的中間位置。