为什么使用alloca()不被认为是好的做法?
alloca()
从堆栈分配内存,而不是malloc()
堆。 所以,当我从例程返回时,内存被释放。 所以,这实际上解决了我释放dynamic分配内存的问题。 释放通过malloc()
分配的内存是一个非常头疼的问题,如果不知何故,会导致各种内存问题。
为什么使用alloca()
,尽pipe有上述的特性,
答案就在man
页(至less在Linux上):
返回值alloca()函数返回一个指向分配空间开始的指针。 如果分配导致堆栈溢出,程序行为是不确定的。
这不是说它永远不会被使用。 我所从事的OSS项目之一就是广泛使用它,只要你不滥用( alloca
巨大的价值),就没有问题。 一旦你通过“几百字节”的标记,是时候使用malloc
和朋友,而不是。 你仍然可能会得到分配失败,但至less你会有一些失败的迹象,而不是只是吹出堆栈。
我遇到的最令人难忘的错误之一是使用alloca
的内联函数。 它在程序执行的随机点上performance为堆栈溢出(因为它在堆栈上分配)。
在头文件中:
void DoSomething() { wchar_t* pStr = alloca(100); //...... }
在执行文件中:
void Process() { for (i = 0; i < 1000000; i++) { DoSomething(); } }
所以发生了什么事是编译器内嵌了DoSomething
函数,并且所有的堆栈分配都在Process()
函数内发生,从而使堆栈上升。 在我的辩护(我不是发现这个问题的人,我不得不去找一个高级开发人员,当我不能修复它),这不是直接alloca
,它是ATLstring之一转换macros。
所以教训是 – 不要在你认为可能被内联的函数中使用alloca
。
老问题,但没有人提到它应该被可变长度的数组所取代。
char arr[size];
代替
char *arr=alloca(size);
它在标准的C99中,在许多编译器中作为编译器扩展存在。
如果你不能使用一个标准的局部variables,alloca()是非常有用的,因为它的大小需要在运行时确定,你可以绝对保证你从alloca()得到的指针永远不会在这个函数返回之后被使用 。
如果你可以相当安全
- 不要返回指针或任何包含它的东西。
- 不要将指针存储在堆中分配的任何结构中
- 不要让其他线程使用指针
真正的危险来自其他人稍后违反这些条件的可能性。 考虑到这一点,将缓冲区传递给格式化文本的函数是非常好的:)
正如在这个新闻组发布中指出的那样,使用alloca
可以被认为是困难和危险的有几个原因:
- 并不是所有的编译器都支持
alloca
。 - 一些编译器不同地解释了
alloca
的预期行为,所以即使在支持它的编译器之间也不保证可移植性。 - 一些实现是越野车。
一个问题是,虽然得到了广泛的支持,但并不标准。 其他事情是平等的,我总是使用一个标准的function,而不是一个普通的编译器扩展。
仍然alloca使用不鼓励,为什么?
我不认为这样的共识。 有很多优点 一些缺点:
- C99提供了可变长度的数组,这些数组通常会优先使用,因为符号与固定长度的数组更加一致,
- 许多系统对堆栈的可用内存/地址空间比对堆的可用空间less,这使得程序稍微容易受到内存耗尽的影响(通过堆栈溢出):这可能被视为一个好事或坏事 – 一个堆栈不会自动增长堆栈的原因是为了防止失控程序对整个机器具有同样的不利影响
- 当在一个更局部的作用域(比如
while
或者for
循环)或者在几个作用域中使用的时候,内存会在每个迭代/作用域累积,直到函数退出时才会释放:这与控制结构中定义的正常variables(例如,for {int i = 0; i < 2; ++i) { X }
会累积X请求的alloca
-ed内存,但是固定大小的数组的内存将在每次迭代中被回收)。 - 现代编译器通常不会
inline
调用alloca
函数,但是如果强制它们,那么alloca
将发生在调用者的上下文中(即直到调用者返回时才会释放该栈) - 很久以前的
alloca
从非便携式function转换到标准化扩展,但是一些负面看法可能会持续下去 - 生命周期必然与函数的作用域有关,它可能会或可能不会比
malloc
的显式控制更适合程序员 - 必须使用
malloc
鼓励思考释放 – 如果通过包装函数(例如WonderfulObject_DestructorFree(ptr)
)来pipe理,那么函数为实现清理操作(如closures文件描述符,释放内部指针或执行一些日志logging)提供了一个点。没有对客户端代码进行明确的修改:有时候这是一个不错的模式- 在这种伪OO风格的编程中,很自然的想要像
WonderfulObject* p = WonderfulObject_AllocConstructor();
– 当“构造函数”是一个返回malloc
-ed内存的函数时,这是可能的(因为在函数返回要存储在p
的值之后内存仍然分配),但是如果“构造函数”使用alloca
-
WonderfulObject_AllocConstructor
一个macros版本可以实现这个function,但是“macros是邪恶的”,因为它们可以相互冲突,并且可以产生非macros代码,从而产生意想不到的替代效果,从而导致难以诊断的问题
-
- ValGrind,Purify等可以检测到丢失的
free
操作,但是根本不可能检测到“析构函数”调用的缺失 – 在预期使用的执行方面非常脆弱; 一些alloca()
实现(比如GCC)使用内联macros来实现alloca()
,所以运行时内存使用诊断库的replace不可能像malloc
/realloc
/free
(例如电子围栏)
- 在这种伪OO风格的编程中,很自然的想要像
- 一些实现有一些微妙的问题:例如,从Linux的手册页:
在许多系统上,alloca()不能在函数调用的参数列表中使用,因为alloca()保留的栈空间会出现在函数参数空间中间的堆栈上。
我知道这个问题被标记为C,但作为一个C ++程序员,我想我会用C ++来说明alloca
的潜在效用:下面的代码( 在ideone中 )创build一个vector跟踪不同大小的多态types,堆栈分配与生命期绑定函数返回)而不是堆分配。
#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()
或其他更快,更方便。
换句话说, alloca( 0x00ffffff )
是危险的,可能会导致溢出,正如char hugeArray[ 0x00ffffff ];
是。 谨慎和合理,你会没事的。
大家都已经指出了堆栈溢出这个潜在的未定义行为这个大问题,但是我要提一下,Windows环境有一个很好的机制来使用结构化exception(SEH)和守护页来解决这个问题。 由于堆栈只根据需要增长,因此这些防护页面将驻留在未分配的区域。 如果你分配给它们(通过溢出堆栈)抛出一个exception。
你可以捕获这个SEHexception,并调用_resetstkoflw来重置堆栈并继续快乐的方式。 它不是理想的,但它是另一种机制,至less知道什么东西出了问题时,东西击中球迷。 *尼克斯可能有类似的东西,我不知道。
我build议通过封装alloca并在内部跟踪来限制最大分配大小。 如果你真的死硬的话,你可以在函数的顶部放一些示波器函数来跟踪函数范围内的任何alloca分配,并根据项目允许的最大值进行检查。
此外,除了不允许内存泄漏alloca不会导致内存碎片这是非常重要的。 我不认为alloca是不好的做法,如果你聪明地使用它,这基本上是对所有事情都是正确的。 🙂
原因如下:
char x; char *y=malloc(1); char *z=alloca(&x-y); *z = 1;
不是任何人都会写这个代码,但是你传递给alloca
的大小参数几乎肯定来自某种input,这可能会恶意地让你的程序分配一些类似的东西。 毕竟,如果大小不是基于input或不可能很大,为什么不只是声明一个小的,固定大小的本地缓冲区?
几乎所有使用alloca
和/或C99 vlas的代码都有严重的错误,这会导致崩溃(如果幸运的话)或特权折中(如果你不那么幸运)。
alloca()很好,很高效……但是它也被深深打破了。
- 打破范围行为(function范围,而不是块范围)
- 使用不一致的malloc( alloca() – 指针不应该被释放,今后你必须跟踪你的指针来自free()只有那些你用malloc() )
- 当您也使用内联时,行为不正确(有时根据被调用者是否被内联,范围会转到调用者函数)。
- 没有堆栈边界检查
- 未定义的行为在失败的情况下(不像malloc那样返回NULL …失败意味着什么,因为它不检查堆栈边界…)
- 不是ansi标准
在大多数情况下,您可以使用局部variables和主体大小来replace它。 如果它用于大型对象,把它们放在堆上通常是一个更安全的想法。
如果你真的需要它,你可以使用VLA(C ++中没有vla,太糟糕了)。 关于范围行为和一致性,它们比alloca()好得多。 正如我所看到的, VLA是一种alloca()制造的。
当然,使用所需空间的majorant的局部结构或数组仍然更好,如果你没有使用普通的malloc()这样的majorant堆分配可能是理智的。 我没有看到真正需要alloca()或VLA的理智用例。
很多有趣的答案,这个“老”的问题,甚至一些相对较新的答案,但我没有发现任何提及这个….
如果使用得当,小心谨慎地使用
alloca()
(可能在整个应用程序范围内)来处理小的可变长度分配(或可用的C99 VLA),可能会导致整体堆栈增长低于使用超大本地数组固定的长度。 所以,如果你仔细使用它,alloca()
可能对你的堆栈有好处 。
我发现引用….好,我把这个报价了。 但是,真的,想想看….
@j_random_hacker在其他答案的评论中是非常正确的:避免使用alloca()
来支持超大的本地数组不会让你的程序更安全,因为堆栈溢出(除非你的编译器足够长以允许内联使用alloca()
的函数alloca()
在这种情况下,你应该升级,或者除非你在循环内使用alloca()
,在这种情况下你应该…不要在循环内部使用alloca()
)。
我曾在桌面/服务器环境和embedded式系统上工作。 很多embedded式系统根本不使用堆(甚至没有链接支持它),原因包括dynamic分配的内存是恶意的,这是由于应用程序的内存泄漏风险决定的一次重新启动多年,或者更合理的理由认为dynamic内存是危险的,因为它不能确定应用程序永远不会将其堆栈碎片化为虚假内存耗尽的地步。 所以embedded式程序员只剩下几个select。
alloca()
(或者VLA)可能只是这个工作的正确工具。
我曾经一次又一次地看到程序员在一个堆栈分配的缓冲区中“足够大以处理任何可能的情况”。 在深度嵌套的调用树中,反复使用该(反 – ?)模式会导致堆栈过度使用。 (设想一个20级的调用树,在每个级别出于不同的原因,函数盲目地过度分配一个1024字节的缓冲区“为了安全”,一般情况下它只使用16个或更less,只有在非常less数情况下可能会使用更多)。另一种方法是使用alloca()
或VLA,并且只分配尽可能多的堆栈空间,以避免不必要的堆栈负担。 希望当调用树中的一个函数需要大于正常的分配时,调用树中的其他部分仍然使用正常的小分配,并且整个应用程序堆栈的使用量要比每个函数盲目地超额分配一个本地缓冲区。
但是如果你select使用alloca()
…
根据本页上的其他答案,似乎VLA应该是安全的(如果在循环中调用,它们不会复合堆栈分配),但是如果使用alloca()
,请注意不要在循环中使用它,并确保你的函数不能被内联,如果有可能在另一个函数的循环中被调用。
一个比malloc()
特别危险的地方是内核 – 一个典型操作系统的内核有一个固定大小的堆栈空间,硬编码到它的一个头部; 它不像应用程序的堆栈那么灵活。 以不合理的大小调用alloca()
可能会导致内核崩溃。 某些编译器在编译内核代码时应该打开某些选项,警告使用alloca()
(甚至是VLA) – 在这里,最好在堆中分配内存,编码限制。
进程只有有限的可用堆栈空间 – 远远小于malloc()
可用的内存量。
通过使用alloca()
你大大增加了获得堆栈溢出错误的机会(如果你幸运的话,或者如果你不幸的话会造成莫名其妙的崩溃)。
不幸的是真正真棒的alloca()
从几乎真棒的tcc中丢失了。 Gcc确实有alloca()
。
-
它播下了自己毁灭的种子。 用return作为析构函数。
-
像
malloc()
它会返回一个无效的指针失败,这将在具有MMU的现代系统上进行段错误(并希望重新启动它们)。 -
与自动variables不同,您可以在运行时指定大小。
它适用于recursion。 您可以使用静态variables来实现类似于尾recursion的操作,并使用其他几个将信息传递给每个迭代。
如果你推的太深,你可以确定一个段错误(如果你有一个MMU)。
请注意,当系统内存不足时, malloc()
不会再提供,因为它返回NULL(如果已分配,也会返回segfault)。 即所有你能做的就是保释或者只是试图以任何方式分配。
要使用malloc()
我使用全局variables并将它们分配为NULL。 如果指针不是NULL,则在使用malloc()
之前释放它。
如果需要复制现有数据,也可以使用realloc()
作为一般情况。 如果要在realloc()
之后进行复制或连接,则需要先检查指针。
3.2.5.2 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:
-
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).
-
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.
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; (...) }
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. I know of a RISC machine where functions using alloca need to use an extra pointer register (stack & frame) throughout the function, whereas normal functions need just one. This is one less register available for general use.
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.
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.
为什么这不好?
IMHO, alloca is considered bad practice because everybody is afraid of exhausting the stack size limit.
I learned much by reading this thread and some other links:
- https://unix.stackexchange.com/questions/63742/what-is-automatic-stack-expansion
- Stack allocation limit for programs on a Linux 32 bit machine
- ulimit -s
I use alloca mainly to make my plain C files compilable on msvc and gcc without any change, C89 style, no #ifdef _MSC_VER, etc.
谢谢 ! This thread made me sign up to this site 🙂
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.
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.
Most answers here largely miss the point: there's a reason why using _alloca()
is potentially worse than merely storing large objects in the stack.
The main difference between automatic storage and _alloca()
is that the latter suffers from an additional (serious) problem: the allocated block is not controlled by the compiler , so there's no way for the compiler to optimize or recycle it.
比较:
while (condition) { char buffer[0x100]; // Chill. /* ... */ }
有:
while (condition) { char* buffer = _alloca(0x100); // Bad! /* ... */ }
The problem with the latter should be obvious.