stack不同 - malloc c用法




為什麼使用alloca()不被認為是好的做法? (16)

仍然alloca使用不鼓勵,為什麼?

我不認為這種共識。 很多優秀的專業人士; 一些缺點:

  • C99提供了可變長度的數組,這些數組通常會優先使用,因為符號與固定長度數組更加一致,並且整體直觀
  • 許多系統對堆棧的可用內存/地址空間比對堆的可用空間少,這使得程序稍微容易受到內存耗盡的影響(通過堆棧溢出):這可能被認為是好事或壞事 - 一個堆棧不會自動增長堆棧的原因是為了防止失控程序對整個機器造成太多不利影響
  • 當用於更局部的作用域(比如while或者for循環)或者在幾個作用域中時,內存會在每個迭代/作用域累積,直到函數退出時才會釋放:這與控制結構範圍中定義的正常變量形成對比(例如, for {int i = 0; i < 2; ++i) { X }會累積X請求的分配內存,但固定大小數組的內存將在每次迭代中循環使用)。
  • 現代編譯器通常不會inline調用alloca函數,但是如果強制它們,那麼alloca將發生在調用者的上下文中(即,在調用者返回之前不會釋放該堆棧)
  • 很久以前的alloca從非便攜式特性/黑客轉變為標準化擴展,但是一些負面看法可能會持續下去
  • 生命週期與函數範圍相關,這可能會或可能不適合程序員比malloc的顯式控制更好
  • 必須使用malloc鼓勵思考釋放 - 如果這是通過包裝函數(例如WonderfulObject_DestructorFree(ptr) )管理的,那麼該函數為實現清理操作提供了一個點(如關閉文件描述符,釋放內部指針或執行一些日誌記錄)而不需要對客戶端代碼進行明確的修改:有時這是一個不錯的模式
    • 在這種偽OO風格的編程中,很自然地想要像WonderfulObject* p = WonderfulObject_AllocConstructor(); - 當“構造函數”是一個返回malloc -ed內存的函數時(這是因為在函數返回要存儲在p的值之後,內存仍然分配),但如果“構造函數”使用alloca
      • 一個宏版本的WonderfulObject_AllocConstructor可以實現這一目標,但是“宏是邪惡的”,因為它們可能會相互衝突,並且會產生非宏代碼,並且會產生意外的替換並導致難以診斷的問題
    • ValGrind,Purify等可以檢測到缺失的free操作,但根本無法檢測到“析構函數”調用的缺失 - 在預期使用的執行方面有一個非常微弱的好處; 一些alloca()實現(比如GCC)使用內聯宏來實現alloca() ,所以運行時內存使用診斷庫的替換不可能像malloc / realloc / free (例如電子圍欄)
  • 一些實現有一些細微的問題:例如,從Linux的手冊頁:

    在許多系統上,alloca()不能在函數調用的參數列表中使用,因為由alloca()保留的棧空間將出現在函數參數空間中間的棧上。

我知道這個問題被標記為C,但作為一名C ++程序員,我認為我會用C ++來說明alloca的潛在效用:下面的代碼(以及ideone處的代碼 )創建了一個矢量跟踪堆棧分配的不同大小的多態類型與生命期綁定函數返回)而不是堆分配。

#include <alloca.h>
#include <iostream>
#include <vector>

struct Base
{
    virtual ~Base() { }
    virtual int to_int() const = 0;
};

struct Integer : Base
{
    Integer(int n) : n_(n) { }
    int to_int() const { return n_; }
    int n_;
};

struct Double : Base
{
    Double(double n) : n_(n) { }
    int to_int() const { return -n_; }
    double n_;
};

inline Base* factory(double d) __attribute__((always_inline));

inline Base* factory(double d)
{
    if ((double)(int)d != d)
        return new (alloca(sizeof(Double))) Double(d);
    else
        return new (alloca(sizeof(Integer))) Integer(d);
}

int main()
{
    std::vector<Base*> numbers;
    numbers.push_back(factory(29.3));
    numbers.push_back(factory(29));
    numbers.push_back(factory(7.1));
    numbers.push_back(factory(2));
    numbers.push_back(factory(231.0));
    for (std::vector<Base*>::const_iterator i = numbers.begin();
         i != numbers.end(); ++i)
    {
        std::cout << *i << ' ' << (*i)->to_int() << '\n';
        (*i)->~Base();   // optionally / else Undefined Behaviour iff the
                         // program depends on side effects of destructor
    }
}

alloca()從堆棧中分配內存,而不是malloc()堆。 所以,當我從例程返回時,內存被釋放。 所以,實際上這解決了我釋放動態分配內存的問題。 釋放通過malloc()分配的內存是一個非常頭痛的問題,如果不知何故錯過了會導致各種內存問題。

儘管有上述特徵,為什麼使用alloca()不鼓勵?


alloca()malloc()特別危險的地方是內核 - 典型操作系統的內核有一個固定大小的堆棧空間,硬編碼到它的一個頭部; 它不像應用程序的堆棧那麼靈活。 以不合理的大小調用alloca()可能會導致內核崩潰。 某些編譯器在編譯內核代碼時應該打開的某些選項中警告使用alloca() (甚至是VLA) - 在這裡,最好在堆中分配內存,編碼限制。


Actually, alloca is not guaranteed to use the stack. Indeed, the gcc-2.95 implementation of alloca allocates memory from the heap using malloc itself. Also that implementation is buggy, it may lead to a memory leak and to some unexpected behavior if you call it inside a block with a further use of goto. Not, to say that you should never use it, but some times alloca leads to more overhead than it releaves frome.


I don't think anyone has mentioned this: Use of alloca in a function will hinder or disable some optimizations that could otherwise be applied in the function, since the compiler cannot know the size of the function's stack frame.

For instance, a common optimization by C compilers is to eliminate use of the frame pointer within a function, frame accesses are made relative to the stack pointer instead; so there's one more register for general use. But if alloca is called within the function, the difference between sp and fp will be unknown for part of the function, so this optimization cannot be done.

Given the rarity of its use, and its shady status as a standard function, compiler designers quite possibly disable any optimization that might cause trouble with alloca, if would take more than a little effort to make it work with alloca.


If you accidentally write beyond the block allocated with alloca (due to a buffer overflow for example), then you will overwrite the return address of your function, because that one is located "above" on the stack, ie after your allocated block.

The consequence of this is two-fold:

  1. The program will crash spectacularly and it will be impossible to tell why or where it crashed (stack will most likely unwind to a random address due to the overwritten frame pointer).

  2. It makes buffer overflow many times more dangerous, since a malicious user can craft a special payload which would be put on the stack and can therefore end up executed.

In contrast, if you write beyond a block on the heap you "just" get heap corruption. The program will probably terminate unexpectedly but will unwind the stack properly, thereby reducing the chance of malicious code execution.


In my opinion, alloca(), where available, should be used only in a constrained manner. Very much like the use of "goto", quite a large number of otherwise reasonable people have strong aversion not just to the use of, but also the existence of, alloca().

For embedded use, where the stack size is known and limits can be imposed via convention and analysis on the size of the allocation, and where the compiler cannot be upgraded to support C99+, use of alloca() is fine, and I've been known to use it.

When available, VLAs may have some advantages over alloca(): The compiler can generate stack limit checks that will catch out-of-bounds access when array style access is used (I don't know if any compilers do this, but it can be done), and analysis of the code can determine whether the array access expressions are properly bounded. Note that, in some programming environments, such as automotive, medical equipment, and avionics, this analysis has to be done even for fixed size arrays, both automatic (on the stack) and static allocation (global or local).

On architectures that store both data and return addresses/frame pointers on the stack (from what I know, that's all of them), any stack allocated variable can be dangerous because the address of the variable can be taken, and unchecked input values might permit all sorts of mischief.

Portability is less of a concern in the embedded space, however it is a good argument against use of alloca() outside of carefully controlled circumstances.

Outside of the embedded space, I've used alloca() mostly inside logging and formatting functions for efficiency, and in a non-recursive lexical scanner, where temporary structures (allocated using alloca() are created during tokenization and classification, then a persistent object (allocated via malloc()) is populated before the function returns. The use of alloca() for the smaller temporary structures greatly reduces fragmentation when the persistent object is allocated.


Not very pretty, but if performance really matter, you could preallocate some space on the stack.

If you already now the max size of the memory block your need and you want to keep overflow checks, you could do something like :

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}

One pitfall with alloca is that longjmp rewinds it.

That is to say, if you save a context with setjmp , then alloca some memory, then longjmp to the context, you may lose the alloca memory (without any sort of notice). The stack pointer is back where it was and so the memory is no longer reserved; if you call a function or do another alloca , you will clobber the original alloca .

Incidentally, this provides a plausible mechanism for deliberately freeing memory that was allocated with alloca .

This isn't documented anywhere; of course the ISO C description of setjmp will say nothing about interactions with alloca , since it describes no such function. Implementations that provide alloca tend not to document this either.

The focus is usually on the concept that alloca memory is associated with a function activation, not with any block; that multiple invocations of alloca just grab more stack memory which is all released when the function terminates. Not so; the memory is actually associated with the procedure context. When the context is restored with longjmp , so is the prior alloca state.

I have run into this which is how I know.


The alloca function is great and and all the naysayers are simply spreading FUD.

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

Array and parray are EXACTLY the same with EXACTLY the same risks. Saying one is better than another is a syntactic choice, not a technical one.

As for choosing stack variables vs heap variables, there are a LOT of advantages to long running programs using stack over heap for variables with in-scope lifetimes. You avoid heap fragmentation and you can avoid growing your process space with unused (unusable) heap space. You don't need to clean it up. You can control the stack allocation on the process.

Why is this bad?


一個問題是它不是標準的,儘管它得到了廣泛的支持。 在其他條件相同的情況下,我總是使用標準函數而不是通用的編譯器擴展。


可悲的是,真正真棒的alloca()從幾乎真棒的tcc中丟失了。 Gcc確實有alloca()

  1. 它播下了自己毀滅的種子。 用return作為析構函數。

  2. malloc()一樣,它會在失敗時返回一個無效的指針,這會在具有MMU的現代系統上進行段錯誤(並希望重新啟動它們)。

  3. 與自動變量不同,您可以在運行時指定大小。

它適用於遞歸。 您可以使用靜態變量來實現類似於尾部遞歸的操作,並使用其他幾個將信息傳遞給每個迭代的方法。

如果你推得太深,你可以確定一個段錯誤(如果你有一個MMU)。

請注意, malloc()提供更多,因為它在系統內存不足時返回NULL(如果已分配,則會返回segfault)。 即你所能做的只是保釋或者試圖以任何方式分配。

要使用malloc()我使用全局變量並將它們分配為NULL。 如果指針不是NULL,我在使用malloc()之前釋放它。

如果要復制任何現有數據,也可以使用realloc()作為一般情況。 如果要在realloc()之後進行複製或連接,您需要先檢查指針。

3.2.5.2 alloca的優點


如果你不能使用標準局部變量,alloca()是非常有用的,因為它的大小需要在運行時確定,你可以絕對保證你從alloca()得到的指針永遠不會在這個函數返回後被使用

如果你可以相當安全

  • 不要返回指針或任何包含它的指針。
  • 不要將指針存儲在堆中分配的任何結構中
  • 不要讓其他線程使用指針

真正的危險來自別人稍後會違反這些條件的可能性。 考慮到這一點,將緩衝區傳遞給格式化文本的函數是非常好的:)


我遇到的最難忘的錯誤之一是使用alloca的內聯函數。 它在程序執行的隨機點表現為堆棧溢出(因為它在堆棧上分配)。

在頭文件中:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

在實現文件中:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

所以發生了什麼事情是編譯器內嵌了DoSomething函數,並且所有的堆棧分配都發生在Process()函數內部,從而使堆棧上升。 在我的辯護(我不是那個發現這個問題的人,當我不能修復它時,我不得不去找一個高級開發人員哭),這不是直接的alloca ,它是ATL字符串之一轉換宏。

所以課程是 - 不要在你認為可能被內聯的函數中使用alloca


所有其他答案都是正確的。 但是,如果你想使用alloca()分配的東西相當小,我認為這是一種比使用malloc()或其他方法更快,更方便的好技術。

換句話說, alloca( 0x00ffffff )是危險的,可能會導致溢出,正如char hugeArray[ 0x00ffffff ]; 是。 謹慎和合理,你會沒事的。


每個人都已經指出了堆棧溢出中潛在的未定義行為,但我應該提及的是,Windows環境有一個很好的機制來使用結構化異常(SEH)和守護頁來捕獲這個問題。 由於堆棧只根據需要增長,因此這些防護頁駐留在未分配的區域。 如果你分配給它們(通過溢出堆棧)拋出一個異常。

你可以捕捉到這個SEH異常,並調用_resetstkoflw來重置堆棧並繼續快樂的方式。 它不是理想的,但它是另一種機制,至少知道東西擊中粉絲時出了什麼問題。 *尼克斯可能有類似的東西,我不知道。

我建議通過封裝alloca並在內部跟踪它來限制最大分配大小。 如果你真的對它核心的話,你可以在你的函數的頂部拋出一些示波器哨兵來跟踪函數範圍內的任何alloca分配,並根據項目允許的最大金額進行檢查。

此外,除了不允許內存洩漏,alloca不會導致內存碎片,這非常重要。 如果你聰明地使用它,我不認為alloca是不好的做法,這對任何事情都基本上是正確的。 :-)


答案就在man頁(至少在Linux上):

返回值alloca()函數返回一個指向分配空間開始的指針。 如果分配導致堆棧溢出,則程序行為未定義。

這並不是說它永遠不會被使用。 我工作的OSS項目之一廣泛使用它,只要你不濫用( alloca巨大的價值),就沒有問題。 一旦你過去了“幾百字節”的標記,現在是時候使用malloc和朋友了。 你仍然可能會得到分配失敗,但至少你會有一些失敗的跡象,而不是把堆棧炸掉。





alloca