Alloca()在堆栈上分配内存,而不是像malloc()那样在堆上分配内存。因此,当我从例程返回时,内存被释放。这实际上解决了动态分配内存的问题。通过malloc()释放分配的内存是一个非常令人头痛的问题,如果以某种方式错过释放会导致各种内存问题。
尽管有上述特性,为什么不鼓励使用alloca() ?
Alloca()在堆栈上分配内存,而不是像malloc()那样在堆上分配内存。因此,当我从例程返回时,内存被释放。这实际上解决了动态分配内存的问题。通过malloc()释放分配的内存是一个非常令人头痛的问题,如果以某种方式错过释放会导致各种内存问题。
尽管有上述特性,为什么不鼓励使用alloca() ?
当前回答
我遇到的最令人难忘的错误之一是与一个使用alloca的内联函数有关。它在程序执行的随机点表现为堆栈溢出(因为它在堆栈上分配)。
在头文件中:
void DoSomething() {
wchar_t* pStr = alloca(100);
//......
}
在实现文件中:
void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
}
因此,发生的事情是编译器内联DoSomething函数,所有的堆栈分配都发生在Process()函数内部,从而使堆栈膨胀。在我的辩护中(我不是发现这个问题的人;当我无法修复它时,我不得不去和一个高级开发人员哭),它不是直接的分配,它是ATL字符串转换宏之一。
所以教训是-不要在你认为可能内联的函数中使用alloca。
其他回答
仍然不鼓励使用分配,为什么?
我没有看到这样的共识。很多强大的专业人士;一些缺点:
C99 provides variable length arrays, which would often be used preferentially as the notation's more consistent with fixed-length arrays and intuitive overall many systems have less overall memory/address-space available for the stack than they do for the heap, which makes the program slightly more susceptible to memory exhaustion (through stack overflow): this may be seen as a good or a bad thing - one of the reasons the stack doesn't automatically grow the way heap does is to prevent out-of-control programs from having as much adverse impact on the entire machine when used in a more local scope (such as a while or for loop) or in several scopes, the memory accumulates per iteration/scope and is not released until the function exits: this contrasts with normal variables defined in the scope of a control structure (e.g. for {int i = 0; i < 2; ++i) { X } would accumulate alloca-ed memory requested at X, but memory for a fixed-sized array would be recycled per iteration). modern compilers typically do not inline functions that call alloca, but if you force them then the alloca will happen in the callers' context (i.e. the stack won't be released until the caller returns) a long time ago alloca transitioned from a non-portable feature/hack to a Standardised extension, but some negative perception may persist the lifetime is bound to the function scope, which may or may not suit the programmer better than malloc's explicit control having to use malloc encourages thinking about the deallocation - if that's managed through a wrapper function (e.g. WonderfulObject_DestructorFree(ptr)), then the function provides a point for implementation clean up operations (like closing file descriptors, freeing internal pointers or doing some logging) without explicit changes to client code: sometimes it's a nice model to adopt consistently in this pseudo-OO style of programming, it's natural to want something like WonderfulObject* p = WonderfulObject_AllocConstructor(); - that's possible when the "constructor" is a function returning malloc-ed memory (as the memory remains allocated after the function returns the value to be stored in p), but not if the "constructor" uses alloca a macro version of WonderfulObject_AllocConstructor could achieve this, but "macros are evil" in that they can conflict with each other and non-macro code and create unintended substitutions and consequent difficult-to-diagnose problems missing free operations can be detected by ValGrind, Purify etc. but missing "destructor" calls can't always be detected at all - one very tenuous benefit in terms of enforcement of intended usage; some alloca() implementations (such as GCC's) use an inlined macro for alloca(), so runtime substitution of a memory-usage diagnostic library isn't possible the way it is for malloc/realloc/free (e.g. electric fence) some implementations have subtle issues: for example, from the Linux manpage:
在许多系统中,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
}
}
老问题了,但是没有人提到它应该被可变长度数组取代。
char arr[size];
而不是
char *arr=alloca(size);
它存在于标准C99中,并作为编译器扩展存在于许多编译器中。
我遇到的最令人难忘的错误之一是与一个使用alloca的内联函数有关。它在程序执行的随机点表现为堆栈溢出(因为它在堆栈上分配)。
在头文件中:
void DoSomething() {
wchar_t* pStr = alloca(100);
//......
}
在实现文件中:
void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
}
因此,发生的事情是编译器内联DoSomething函数,所有的堆栈分配都发生在Process()函数内部,从而使堆栈膨胀。在我的辩护中(我不是发现这个问题的人;当我无法修复它时,我不得不去和一个高级开发人员哭),它不是直接的分配,它是ATL字符串转换宏之一。
所以教训是-不要在你认为可能内联的函数中使用alloca。
Alloca()很好,很有效……但它也被深深打破了。
broken scope behavior (function scope instead of block scope) use inconsistant with malloc (alloca()-ted pointer shouldn't be freed, henceforth you have to track where you pointers are coming from to free() only those you got with malloc()) bad behavior when you also use inlining (scope sometimes goes to the caller function depending if callee is inlined or not). no stack boundary check undefined behavior in case of failure (does not return NULL like malloc... and what does failure means as it does not check stack boundaries anyway...) not ansi standard
在大多数情况下,您可以使用局部变量和主要大小来替换它。如果它用于大型对象,将它们放在堆上通常是一个更安全的想法。
如果你真的需要它,你可以使用VLA(在c++中没有VLA,太糟糕了)。在作用域行为和一致性方面,它们比alloca()要好得多。在我看来,VLA是一种正确的分配。
当然,使用所需空间的主要部分的本地结构或数组仍然更好,如果没有这样的主要堆分配,则使用普通malloc()可能是明智的。 我没有看到你真的真的需要alloca()或VLA的用例。
在我看来,分配和变长数组的最大风险是,如果分配的大小出乎意料地大,它可能会以非常危险的方式失败。
堆栈上的分配通常没有检入用户代码。
现代操作系统通常会在*下面放置一个保护页面,以检测堆栈溢出。当堆栈溢出时,内核可能会扩展堆栈或杀死进程。Linux在2017年将这个保护区域扩展到比页面大得多,但它的大小仍然是有限的。
因此,作为一条规则,在使用之前的分配之前,最好避免在堆栈上分配超过一个页面。使用分配或可变长度数组,很容易让攻击者在堆栈上进行任意大小分配,从而跳过任何保护页并访问任意内存。
*在当今最广泛的系统中,堆栈向下增长。