alloca()在堆栈上而不是在堆上分配内存,如malloc()的情况。 所以,当我从例程返回时,内存被释放。 所以,实际上这解决了我释放动态分配内存的问题。 释放通过malloc()分配的内存是一个令人头痛的问题,如果不知何故错过会导致各种内存问题。
尽管有上述特征,为什么不鼓励使用alloca()?
-
快速说明一下。虽然这个功能可以在大多数编译器中找到,但它不是ANSI-C标准所要求的,因此可能会限制可移植性。另一件事是,你绝对不能! free()你得到的指针,退出函数后它会自动释放。
-
@meruko好点..一定影响可靠性
-
此外,如果声明为alloca()的函数将不会内联。
-
@Justicle,你能提供一些解释吗?我很好奇这种行为的背后是什么
-
@migajek,Igor在下面添加的答案显示为什么内联是危险的,所以如果Justicle是正确的,它应该是一件好事。
-
忘掉所有关于可移植性的噪音,不需要调用free(这显然是一个优势),无法内联它(显然堆分配非常重)等等。避免alloca的唯一原因是大尺寸。也就是说,浪费大量的堆栈内存并不是一个好主意,而且你有可能出现堆栈溢出。如果是这种情况 - 请考虑使用malloca / freea
-
是否有任何例子,其中alloca()变得非常有用,例如:它可以用于随机化堆栈分配以防止堆栈溢出攻击?或者没有?
-
@ user31986 - 如果在main()中,你在调用actual_main之前做了类似global_ptr_var=alloca( rand( get_seed()) % 12345)的事情,这会使整个程序运行一个(有点)随机堆栈基础。这可能有用。
-
我只是在支持它的平台上使用VLA(可能不是所有C编译器都支持最新标准,因为它们是可选的而不是强制性的,如C99 ......)
-
alloca的另一个积极方面是堆栈不能像堆一样分段。这可以证明对于硬实时运行永久样式应用程序,甚至安全关键应用程序是有用的,因为WCRU可以进行静态分析,而无需借助具有其自身问题集的自定义内存池(没有时间局部性,次优资源)使用)。
答案就在man页面中(至少在Linux上):
RETURN VALUE
The alloca() function returns a pointer to the beginning of the
allocated space. If the
allocation causes
stack overflow, program behaviour is undefined.
这并不是说永远不应该使用它。我工作的其中一个OSS项目广泛使用它,只要你没有滥用它(alloca'巨大的价值),它就没问题了。一旦你超过"几百字节"标记,就可以使用malloc和朋友了。你可能仍然会遇到分配失败,但至少你会有一些失败的迹象,而不是只是吹掉堆栈。
-
那么你真的没有问题,你也不会有声明大数组?
-
@sean我理解这一点,如果我分配了太多内存(因为堆栈空间与堆相比有限),这会导致溢出;但同样是本地数组的情况。如果我保持在我的极限之内那么我应该没事,对吧?然后我还看到反alloa评论。
-
是的,如果你保持在你的限制之内,你的代码可以假定一定的堆栈大小,并且你的函数框架上方的堆栈始终是相同的大小,你应该没问题。
-
与大型阵列不同,因为操作系统会为那些人保留空间......它们的大小非常大。
-
@singpolyma - 以下崩溃对我来说,所以我不确定你的意思:int main(int argc,char * argv){char byebye [1024 * 1024 * 10]; / 10MB阵列* /; byebye [0] = 0;返回0; }
-
@Sean:是的,堆栈溢出风险是给出的原因,但这个原因有点傻。首先是因为(正如Vaibhav所说)大型局部阵列导致完全相同的问题,但几乎不会被诋毁。此外,递归可以很容易地吹掉堆栈。对不起,但是我希望你能够反驳一下流行的观点,即手册页中给出的理由是合理的。
-
我不完全确定你在做什么...不要使用大型本地阵列?应该经常使用未定义行为失败的函数?文档不可信任?
-
我的观点是,手册页中给出的理由没有意义,因为alloca()与被认为是犹太教的其他东西(局部数组或递归函数)完全一样"坏"。
-
我认为手册页中的警告确实有意义。没有理由说alloca无法通过一些魔法调整堆栈大小以适应分配,所以值得指出的是,任何这样的魔法都是依赖于实现的,并且标准不需要。
-
@j_random_hacker:你真的认为大型本地阵列被认为是犹太人吗?
-
@ninjalj:不是由经验丰富的C / C ++程序员,但我确实认为许多担心alloca()的人对本地数组或递归没有同样的恐惧(实际上很多人会大声喊叫alloca()会赞美递归,因为它"看起来很优雅")。我同意Shaun的建议("alloca()适用于小额分配")但我不同意将alloca()框架为3中唯一邪恶的心态 - 它们同样危险!
-
alloca可用于小分配,或者如果您的函数是叶函数(或非常接近叶函数)。这样,您可以在上面设置一个上限,以便从那里使用多少堆栈空间。示例:如果您有一个需要将字符串从ASCII转换为Unicode(或反之亦然)的日志记录功能,则使用alloca而不是malloc作为转换缓冲区会更有效。
-
+1否定@ j_random_hacker的投票结果。手册页非常合理地说明如果alloca()打击堆栈将会发生什么(事实上,不解释这一点是不负责任的)。 Sean的周围解释非常合理,即使是对答案进行限定,也不会被解释为alloca()的彻底禁止。肖恩的回答中没有任何内容暗示了j_random_hacker所说的心态。实际上,肖恩的前两条评论比j_random_hacker的第一条评论提前几个小时发布,非常清楚地表明这不是肖恩的观点。
-
注意:鉴于Linux的"乐观"内存分配策略,你很可能不会得到堆耗尽失败的任何迹象......相反,malloc()将返回一个很好的非NULL指针,然后当你尝试实际访问它指向的地址空间,你的进程(或其他一些过程,不可预测)将被OOM杀手杀死。当然这是Linux的"特性",而不是C / C ++问题本身,但在辩论alloca()或malloc()是否"更安全"时要记住这一点。 :)
-
@j_random_hacker:这真的很简单。您可以在没有alloca的情况下在堆栈上进行固定大小的分配,或者使用alloca进行动态大小的分配。你是对的 - 在任何一种情况下你都要考虑堆栈。
-
我想补充说,不幸的是,当这不是错误的选择时,这个答案没有指定alloca的最大优势:避免删除/免费开销。我通过使用alloca一次使一个函数的速度提高了100倍,因为我删除了堆内存管理开销。
我遇到的最令人难忘的错误之一是使用alloca的内联函数。它表现为堆栈溢出(因为它在堆栈上分配)在程序执行的随机点。
在头文件中:
1 2 3 4
| void DoSomething() {
wchar_t* pStr = alloca(100);
//......
} |
在实现文件中:
1 2 3 4 5
| void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
} |
所以发生的事情是编译器内联DoSomething函数,并且所有堆栈分配都发生在Process()函数内部,从而将堆栈向上吹。在我的辩护中(我不是那个发现问题的人;当我无法修复时,我不得不去找其中一个高级开发人员),它不是直的alloca,它是其中之一ATL字符串转换宏。
所以教训是 - 不要在你认为可能被内联的函数中使用alloca。
-
有趣。但这不会成为编译器错误吗?毕竟,内联改变了代码的行为(它延迟了使用alloca分配的空间的释放)。
-
显然,至少GCC会考虑到这一点:"请注意,函数定义中的某些用法可能使其不适合内联替换。其中包括:使用varargs,使用alloca,[...]"。 gcc.gnu.org/onlinedocs/gcc/Inline.html
-
你吸烟的是什么编译器?
-
Microsoft VC ++ 6。
-
抱歉,我正在编译调试模式! VS2010给出:警告C4750:'bool __cdecl inlined_TestW32Mem5(void)':函数_alloca()内联到循环中
-
那就是你_forceinline(如果它有alloca,那么函数没有内联)
-
我不明白的是为什么编译器没有充分利用范围来确定子范围内的allocas或多或少"被释放":堆栈指针在进入范围之前可以回到它的位置,就像在什么时候做的那样从函数返回(不是吗?)
-
更糟糕的是:一位同事设法在一段时间(!完成)循环中直接使用alloca ...
-
本课程并不意味着不应使用alloca()。它只是意味着您不应该允许编译器内联它。通过在任何调用alloca()的函数上说__attribute__((__noinline__)),可以在GCC和Clang上轻松避免这种情况。问题解决了。
-
@moala - 可能变得复杂,例如如果调用者也在使用alloca。它可能比编译器已经做的事情更难做,但由于'alloca'仍然被认为是一个"边缘"问题,编译器设计者不太可能增加额外的复杂性来支持这一点。相关点:在你的函数中使用alloca将导致优化器"踢"很多东西,一方面它不再知道本地帧的大小,并且必须使用帧寄存器,否则它可能不需要。
-
我已经投了很多,但答案写得很好:我同意其他人的意见,因为这是一个很明显的编译错误。编译器在不应该进行的优化中做出错误的假设。解决编译器错误是很好的,但我不会因为它而对编译器有任何错误。
老问题,但没有人提到它应该被可变长度数组所取代。
代替
它在标准C99中,在许多编译器中作为编译器扩展存在。
-
Jonathan Leffler在评论Arthur Ulfeldt的回答时提到了这一点。
-
确实,但它也显示了它是多么容易错过,因为尽管在发布之前阅读了所有回复,但我还没有看到它。
-
如果只有MSVC2010支持C99 ......
-
一个注意事项 - 那些是可变长度数组,而不是动态数组。后者是可调整大小的,通常在堆上实现。
-
你是对的蒂姆,我改变了措辞。
-
@Benj:这是最近MSVC版本的问题吗?
-
@einpoklum我不相信,虽然我还没有尝试过:msdn.microsoft.com/en-us/library/hh409293.aspx
-
编译某些C ++的Visual Studio 2015具有相同的问题。
-
编译器如何实现可变大小的数组?
-
只需调整堆栈指针即可。当你调用一个函数时,会有一些代码调整sp,有时也会调整一个帧指针,它允许寻址堆栈上分配的局部变量。根据不同变量的大小调整堆栈指针。对于普通变量,value是编译器发出的常量。在VLA的情况下,该值取决于参数或另一个变量。这就是为什么VLA很有趣,它们的分配成本不高,因为它只是一个简单的计算添加到一个必须要完成的操作。
-
Linus Torvalds不喜欢Linux内核中的VLA。从版本4.20开始,Linux应该几乎免于VLA。
如果你不能使用标准的局部变量,alloca()非常有用,因为它的大小需要在运行时确定,你可以
绝对保证在此函数返回后,从alloca()获得的指针永远不会被使用。
如果你,你可以相当安全
-
不要返回指针或包含它的任何内容。
-
不要将指针存储在堆上分配的任何结构中
-
不要让任何其他线程使用指针
真正的危险来自于其他人稍后会违反这些条件的可能性。考虑到这一点,将缓冲区传递给将文本格式化为其中的函数是很好的:)
-
C99的VLA(可变长度数组)功能支持动态大小的局部变量,而无需明确要求使用alloca()。
-
NEATO!在programmersheaven.com/2/Pointers-and-Arrays-page-2的"3.4可变长度数组"部分中找到更多信息
-
但这与使用指向局部变量的指针的处理没有什么不同。他们也可以被愚弄...
-
@Jonathan Leffler你可以用alloca做一件事,但你不能用VLA使用restrict关键字。像这样:float * restrict heavy_used_arr = alloca(sizeof(float)* size);而不是浮动heavy_used_arr [size]。即使size是编译常量,它也可以帮助一些编译器(在我的例子中为gcc 4.8)来优化程序集。请参阅我的问题:stackoverflow.com/questions/19026643/using-restrict-with-arrays
-
@JonathanLeffler VLA是包含它的块的本地。另一方面,alloca()分配持续到函数结束的内存。这意味着似乎没有直接,方便的翻译到f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }的VLA。如果您认为可以自动将alloca的使用转换为VLA的使用,但需要的不仅仅是评论来描述如何,我可以提出这个问题。
-
@PascalCuoq:使用alloca()的代码和使用VLA的代码之间并不总是直接的,更不用说自动翻译了。我观察到在f()中,/* use p */代码可能必须具有进一步的条件,具体取决于p是否为null。使用VLA的代码的要点可能是:f() { int x = 1; if (c) { /* compute x */ } char p[x]; /* use p */ }。
-
一些命中:(1)如果调用者仅使用该指针来寻址统计信息并且从不取消引用它,则返回指针是安全的。 (2)将指针存储在堆上分配的结构中是完全安全的,只要在堆栈帧关闭之前从堆中删除结构即可。我不知道为什么有人会这样做,但它应该是非常安全的。 (3)如果你可以保证内存在它们使用它的整个持续时间内有效,那么让其他线程使用指针是很好的 - 例如,一些长时间运行的计算。
-
我们还要注意,你必须遵循亚瑟提到的与VLA以及alloca()结果相同的限制。
-
很好的答案,以及评论必不可少的一个很好的例子 - 告诉未来的编码员他最好知道他在做什么或者让别人去做。
-
我想补充一点:首先验证大小,如果它取决于不受信任的数据,在调用之前,所以你不要求堆栈帧的荒谬大幅增加。并且(如上所述),这也适用于VLA。
正如本新闻组发布中所述,使用alloca可能被认为是困难和危险的原因有几个:
-
并非所有编译器都支持alloca。
-
有些编译器以不同的方式解释alloca的预期行为,因此即使在支持它的编译器之间也不能保证可移植性。
-
一些实现是错误的。
-
我在该链接上看到的一个不在本页其他地方的内容是使用alloca()的函数需要单独的寄存器来保存堆栈指针和帧指针。在x86 CPU> = 386时,堆栈指针ESP可用于两者,释放EBP - 除非使用alloca()。
-
该页面上的另一个好处是,除非编译器的代码生成器将其作为一种特殊情况处理,否则f(42, alloca(10), 43);可能会崩溃,因为在推送至少一个参数后,堆栈指针可能会被alloca()调整。
-
链接的文章似乎是由约翰莱文撰写的 - 写了"连接器和装载机"的家伙,我会信任他所说的。
-
链接的帖子是John Levine对帖子的回复。
-
+1没有做出无关紧要的论点,比如更高的投票答案,忘记了他们的参数也适用于可变长度数组或任何堆栈耗尽的设备。
-
请记住,自1991年以来发生了很多变化。所有现代C编译器(即使在2009年)都必须处理alloca作为一个特例;它是一个内在的而不是一个普通的函数,甚至可能不会调用函数。因此,参数中的分配问题(从1970年代开始在K&R C中出现)现在应该不是问题。我在Tony D的回答中做出的评论中有更详细的说明
一个问题是它不是标准的,尽管它得到了广泛的支持。在其他条件相同的情况下,我总是使用标准函数而不是常见的编译器扩展。
still alloca use is discouraged, why?
我没有看到这样的共识。很多强大的专业人士;一些缺点:
-
C99提供可变长度数组,这些数组通常会优先使用,因为符号与固定长度数组更加一致并且直观整体
-
许多系统可用于堆栈的总内存/地址空间少于堆,这使得程序稍微更容易受到内存耗尽(通过堆栈溢出):这可能被视为好事或坏事 - 一个堆栈没有按照堆的方式自动增长的原因是为了防止失控程序对整个机器产生同样多的负面影响
-
当在更局部的范围(例如while或for循环)或多个范围中使用时,内存会在每次迭代/范围内累积,并且在函数退出之前不会释放:这与范围中定义的正常变量形成对比控制结构(例如,for {int i = 0; i < 2; ++i) { X }将累积在X处请求的alloca -ed存储器,但是对于固定大小的阵列的存储器将在每次迭代时被再循环)。
-
现代编译器通常不会调用alloca的inline函数,但是如果你强制它们那么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手册页:
On many systems alloca() cannot be used inside the list of arguments of a function call, because the stack space reserved by alloca() would appear on the stack in the middle of the space for the function arguments.
我知道这个问题被标记为C,但作为一名C ++程序员,我认为我会用C ++来说明alloca的潜在效用:下面的代码(这里是ideone)创建一个矢量跟踪不同大小的多态类型,即堆栈已分配(与生命周期绑定的函数返回)而不是堆分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include
#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() << '
';
(*i)->~Base(); // optionally / else Undefined Behaviour iff the
// program depends on side effects of destructor
}
} |
-
没有+1,因为处理几种类型的鱼腥特殊方式:-(
-
@einpoklum:这很有启发性......谢谢。
-
让我重新说一下:这是一个非常好的答案。直到我认为你建议人们使用某种反模式的程度。
-
linux手册页的评论非常陈旧,我很确定,已经过时了。所有现代编译器都知道alloca()是什么,并且不会像这样穿过他们的鞋带。在旧的K&R C中,(1)所有函数都使用了帧指针(2)所有函数调用都是{push args on stack} {call func} {add#n,sp}。 alloca是一个lib函数,只会碰到堆栈,编译器甚至不知道发生了什么。 (1)和(2)不再是真的,所以alloca不能那样工作(现在它是一个内在的)。在旧的C中,在推动args的过程中调用alloca显然也会打破这些假设。
-
关于这个例子,我一般都会关注需要always_inline以避免内存损坏的事情....
所有其他答案都是正确的。但是,如果你想用alloca()分配的东西相当小,我认为这是一种比使用malloc()或其他更快更方便的好技术。
换句话说,alloca( 0x00ffffff )是危险的并且可能导致溢出,与char hugeArray[ 0x00ffffff ];完全一样多。要小心谨慎,你会没事的。
每个人都已经指出了堆栈溢出中潜在的未定义行为这个大问题,但是我应该提到Windows环境有一个很好的机制来使用结构化异常(SEH)和保护页面来捕获它。由于堆栈仅根据需要增长,因此这些保护页面位于未分配的区域中。如果分配给它们(通过溢出堆栈),则抛出异常。
您可以捕获此SEH异常并调用_resetstkoflw重置堆栈并继续您的快乐方式。这不是理想的,但它是另一种机制,至少知道当东西击中粉丝时出现问题。 * nix可能有类似我不知道的东西。
我建议通过包装alloca并在内部跟踪来限制最大分配大小。如果你真的是硬核的话,你可以在你的函数顶部抛出一些范围的哨兵来跟踪函数范围内的任何alloca分配,并且理智地检查你的项目所允许的最大数量。
此外,除了不允许内存泄漏之外,alloca不会导致内存碎片,这非常重要。如果你聪明地使用它,我不认为alloca是不好的做法,这基本上适用于所有事情。 :-)
-
问题是,alloca()可能需要这么大的空间,堆栈指针落在堆中。有了这个,可以控制alloca()大小的攻击者和进入该缓冲区的数据可以覆盖堆(非常糟糕)。
这个"旧"问题有很多有趣的答案,甚至是一些相对较新的答案,但我没有发现任何提及这个问题....
When used properly and with care, consistent use of alloca()
(perhaps application-wide) to handle small variable-length allocations
(or C99 VLAs, where available) can lead to lower overall stack
growth than an otherwise equivalent implementation using oversized
local arrays of fixed length. So alloca() may be good for your stack if you use it carefully.
我发现引用....好吧,我引用了这个引用。但是真的,想一想......
@j_random_hacker在其他答案的评论中是非常正确的:避免使用alloca()支持超大的本地数组不会使您的程序更安全地从堆栈溢出(除非您的编译器足够大以允许内联使用在这种情况下你应该升级,或者除非你使用alloca()内部循环,在这种情况下你应该......不使用alloca()内部循环)。
我曾在桌面/服务器环境和嵌入式系统上工作过。许多嵌入式系统根本不使用堆(它们甚至不支持它),原因包括认为动态分配的内存是邪恶的,因为应用程序上存在内存泄漏的风险多次重启多年,或动态内存危险的更合理的理由,因为无法确定应用程序永远不会将其堆碎到虚假内存耗尽点。因此嵌入式程序员几乎没有其他选择。
alloca()(或VLA)可能只是该工作的正确工具。
我已经一次又一次地看到程序员将堆栈分配的缓冲区"大到足以处理任何可能的情况"。在深度嵌套的调用树中,重复使用该(反 - ?)模式会导致堆栈使用过度。 (想象一下20级深度的调用树,在每个级别出于不同的原因,该函数盲目地过度分配1024字节的缓冲区"只是为了安全",而通常它只会使用16或更少,而且只在非常极少数情况下可能会使用更多。)另一种方法是使用alloca()或VLA并仅分配与函数需要相同的堆栈空间,以避免不必要地增加堆栈负担。希望当调用树中的一个函数需要大于正常的分配时,调用树中的其他函数仍然使用它们的正常小分配,并且整个应用程序堆栈的使用量明显少于每个函数盲目地过度分配本地缓冲区的情况。 。
但是如果你选择使用alloca() ......
基于此页面上的其他答案,似乎VLA应该是安全的(如果从循环内调用它们不会复合堆栈分配),但是如果您使用alloca(),请注意不要在循环内使用它如果有可能在另一个函数的循环中调用它,请确保您的函数无法内联。
-
关于避免重复最坏情况分配的坚定要点....
-
我同意这一点。 alloca()的危险是正确的,但同样可以说malloc()的内存泄漏(为什么不使用GC呢?有人可能会争辩)。小心使用alloca()对减小堆栈大小非常有用。
-
不使用动态内存的另一个好理由,特别是在嵌入式中:它比坚持堆栈更复杂。使用动态内存需要特殊的过程和数据结构,而在堆栈上(简化事情)是从堆栈指针中添加/减去更高的数字。
-
旁注:"使用固定缓冲区[MAX_SIZE]"示例强调了为什么过度使用内存策略如此有效。程序分配的内存可能永远不会触及,除非它们的缓冲区长度有限。因此,Linux(和其他操作系统)在第一次使用之前实际上并没有分配内存页面(与malloc相反)。如果缓冲区大于一页,则程序可能只使用第一页,而不会浪费其余的物理内存。
-
@KatasticVoyage除非MAX_SIZE大于(或至少等于)系统虚拟内存页面大小的大小,否则您的参数不会成立。此外,对于没有虚拟内存的嵌入式系统(许多嵌入式MCU没有MMU),过度使用的内存策略可能是"确保您的程序将在所有情况下运行"的观点,但这种保证伴随着您的堆栈大小的价格必须同样分配以支持过度使用内存策略。在某些嵌入式系统中,这是某些低成本产品制造商不愿意支付的价格。
alloca()既美观又高效......但它也深受打击。
-
破坏范围行为(函数范围而不是块范围)
-
使用与malloc不一致(alloca() - ted指针不应该被释放,因此你必须跟踪你的指针来自free()只有你用malloc()得到的那些)
-
当你也使用内联时,不良行为(范围有时会转到调用者函数,具体取决于被调用者是否内联)。
-
没有堆栈边界检查
-
失败时的未定义行为(不像malloc那样返回NULL ......失败意味着什么,因为它不检查堆栈边界......)
-
不是ansi标准
在大多数情况下,您可以使用局部变量和majorant大小替换它。如果它用于大型对象,将它们放在堆上通常是一个更安全的想法。
如果你真的需要它,你可以使用VLA(在C ++中没有vla,太糟糕了)。它们比alloca()在范围行为和一致性方面要好得多。正如我所看到的,VLA是一种正确的alloca()。
当然,使用所需空间的主要部分的本地结构或数组仍然更好,如果你没有这样的majorant堆分配使用普通的malloc()可能是理智的。
我看到没有理智的用例,你真的需要alloca()或VLA。
-
我没有看到downvote的原因(顺便说一句,没有任何评论)
-
只有名称有范围。 alloca不会创建名称,只会创建具有生命周期的内存范围。
-
@curiousguy:你只是玩文字。对于自动变量,我也可以谈论底层内存的生命周期,因为它与名称的范围相匹配。无论如何,问题不在于我们如何调用它,而是由alloca返回的生命周期/内存范围的不稳定性以及异常行为。
-
我希望alloca有一个相应的"freea",其中一个调用"freea"的规范将撤消创建该对象和所有后续对象的"alloca"的影响,并要求存储'在一个函数中分配'必须在它里面也是'自由的'。这样就可以使几乎所有的实现以兼容的方式支持alloca / freea,可以缓解内联问题,并且通常会使事情变得更加清晰。
-
@supercat - 我也希望如此。出于这个原因(以及更多),我使用抽象层(主要是宏和内联函数),这样我就不会直接调用alloca或malloc或free。我说像{stack|heap}_alloc_{bytes,items,struct,varstruct}和{stack|heap}_dealloc这样的东西。所以,heap_dealloc只调用free而stack_dealloc是无操作。这样,堆栈分配可以很容易地降级到堆分配,并且意图也更清晰。
原因如下:
1 2 3 4
| char x ;
char *y =malloc(1);
char *z =alloca (&x -y );
*z = 1; |
并不是说有人会写这个代码,但是你传递给alloca的大小参数几乎肯定来自某种输入,这可能会恶意地使你的程序变得像那样巨大的东西alloca。毕竟,如果大小不是基于输入或者没有可能变大,为什么不直接声明一个小的,固定大小的本地缓冲区?
几乎所有使用alloca和/或C99 vlas的代码都有严重的错误,这些错误会导致崩溃(如果你很幸运)或特权妥协(如果你不是那么幸运)。
-
世界可能永远不会知道。 :(那就是说,我希望你能澄清一个关于alloca的问题。你说几乎所有使用它的代码都有一个bug,但我打算使用它;我通常会忽略这样一个声明但是来自你我不会。我正在编写一个虚拟机,我想分配不会从堆栈中的函数中逃逸的变量,而不是动态的,因为它具有巨大的加速。有一种具有相同性能特征的替代方法吗?我知道我可以接近内存池,但仍然不那么便宜。你会做什么?
-
随意在堆栈上分配小对象 - 这是安全的 - 但在这种情况下,一个固定大小的缓冲区(例如unsigned char buf[1024];)带有一些简单的代码来管理它并保持对齐也可以正常工作,并且会通过避免alloca更加便携。 VLA或alloca可以为您提供的唯一好处是能够进行任意大的分配(具有所需的危险)或能够进行任意小的分配(例如,当需要少于1024字节时避免浪费堆栈空间)。
-
@R:我明白了,谢谢。
-
知道什么也有危险吗?这:*0 = 9;令人惊叹!我想我永远不应该使用指针(或至少取消引用它们)。呃,等等。我可以测试它是否为null。嗯。我想我也可以通过alloca测试我想要分配的内存大小。奇怪的人。奇怪的。
-
*0=9;无效C.对于测试传递给alloca的大小,请根据什么进行测试?没有办法知道这个限制,如果你只是针对一个很小的固定已知安全大小(例如8k)进行测试,你也可以在堆栈上使用固定大小的数组。
-
你所知道的"你知道的大小是否足够小或者它是依赖于输入的,因此可能是任意大的"这一论点的问题,因为我认为它同样适用于递归。一个实际的妥协(对于这两种情况)是假设如果大小受small_constant * log(user_input)限制,那么我们可能有足够的内存。
-
实际上,您已经确定了VLA / alloca有用的一种情况:递归算法,其中任何调用帧所需的最大空间可能与N一样大,但所有递归级别所需的空间总和为N或某些函数N不快速增长。
-
有见识的答案。是否存在因在攻击者控制的输入上使用alloca()(或基于相同分配VLA)而导致的历史漏洞,可能记录为CVE?
-
没有办法知道限制 - 堆栈可以明确设置[1],所以它可以知道,它只是不太实用。 1 - pthread_attr_setstack
-
@bestsss:这没有用,因为你无法控制编译器使用了多少。显然,真正的编译器旨在保持一定的理智,但它们仍然可以浪费大量的对齐堆栈帧,内联具有大型基于堆栈的缓冲区的函数,并且在内联函数完成后不再重用空间等.BTW pthread_attr_setstack有一些主要的问题,不应该使用。 pthread_attr_setstacksize是首选操作。
-
除了不可移植性有什么问题 - 比如有2个堆栈的架构,一个用于返回地址和三色区域(例如Itanium)的数据一个?我的意思是除了一些困难,正确使用它会导致什么问题?
-
"几乎肯定来自某种投入"?不是我的代码。我使用alloca()来分配可变大小的结构(即,使用可变大小数组作为最后一个元素的结构)。这很棒..
-
@ToddLehman:"可变大小的最后一个元素"的大小来自哪里?
-
@R .. - 一个静态const表。或者在大多数情况下(在我最常使用它的程序中),它是数字的素数因子分解中的因子数量,对于64位整数,其固有的上限为63 - 无论多大,即使它由用户提供。因此,我可以放心地生活在这样一个事实上,即永远不会超过63个元素。事实上,这超出了检查用户提供的输入的尽职调查。在这里,拥有超过63个元素在物理上是不可能的(缺少代码错误)。
-
@ToddLehman:那么使用alloca是没有意义的。使用固定大小的63元素阵列。堆栈使用率不会明显不同,并且您的程序将表现更好(因为alloca需要帧指针寄存器或等效,增加了寄存器压力和本地数据访问的复杂性)。如上所述,几乎唯一不能用常量大小的数组代替alloca的地方是alloca不安全的地方。
-
@R .. - 没有!不好。不能为此使用固定大小的63元素数组,因为那时我必须有两个单独的结构定义(一个用于固定大小的数组,一个用于可变大小的数组),它们依次意味着在结构上运行的函数的两倍。我实际上需要可变大小的数组用于从堆分配的结构。在某些情况下,我可以在堆栈上进行临时处理。只有一个结构可以完成所有这一切真的很好,如果我不得不使用固定大小的数组,那将是一场噩梦。 alloca对此纯粹是甜蜜的。
-
@ToddLehman:较旧的编译器对使用一个函数处理匹配的结构的代码没有问题,除了包含数组的大小。我希望标准能够承认这些技术很有用,并且应该在99%的平台上得到认可,如果"优化器"没有妨碍它们,它们将自然地起作用。
-
@supercat - 但即便如此,为什么还要有两个不同的结构定义(一个用于堆,一个用于堆栈)? alloca非常快。出于所有实际目的,使用alloca在堆栈上分配可变大小的结构与在堆栈上将固定大小的结构分配为变量一样快。我的意思是,当然,它可以有两个不同的定义,并可能做一个演员来保持编译器的快乐,但为什么要这么麻烦?与固定大小的分配相比,动态地在堆栈上分配可变大小的结构可以节省空间,并且不会浪费时间。有蛋糕,也吃吧!
-
@ToddLehman:指定alloca()的方式使得许多编译器无法在没有 - 至少 - 禁用本来有用的优化的情况下支持它。正常实现预先假定在执行alloca()时,在下一次使用帧指针重新加载堆栈指针之前,堆栈上将不需要删除任何内容。如果重新加载没有按预期发生(例如由于函数内嵌),可能会发生坏事。实际上,如果你使用联盟,我认为你可以使用一个函数来处理结构......
-
......在FAM结构和固定大小的阵列之间,足以强制进行所需的分配。如果alloca()有更严格规定的用法,需要使用freea()[可以指定为要求每个alloca()与freea()匹配,或者说任何freea()将释放所有alloca'ed在给定对象之后],然后所有编译器都可以实现它。 LIFO alloc的概念是一个很好的概念,无论它是否使用执行堆栈来保存变量,因为如果代码创建一个估计大小的临时对象,它可以避免碎片...
-
...然后构造一个具有确切大小的新对象,适当地将数据从临时对象格式化为新对象,并且不再需要临时对象。即使实现使用堆作为临时对象,它也可能使分配有些过大,并在使用之间回收相同的对象。
-
@supercat - union不会导致大小等于其中两个大小中的较大者,例如,总是固定大小结构的大小吗?在堆栈分配的情况下,这是可以的,但在堆分配的情况下,它将破坏使用可变大小结构的整个目的。但是,有趣的想法,如果它可以成功。至于便携性......好点。然而,在我的情况下,可移植性不是我关心的,因为我专门为Clang / LLVM和iOS编写,所以这就是为什么我自由地使用alloca。但我理解这些问题。
-
@ToddLehman:你只会使用union来进行堆栈分配,并将其中的对象地址传递给期望堆类型的代码。
-
@supercat - 有趣......可以工作!或者,您可以定义两个结构(一个具有零元素数组作为最后一个元素,另一个具有非零长度数组作为最后一个元素)并转换指针,而不是使用union。不太干净,但它会避免有三个typedef s(一个用于可变大小的结构,一个用于固定大小的结构,一个用于联合),因为它只需要两个typedef。然后,假设alloca可用,alloca仍然可以说比为每个可变长度结构写两个typedef更好。
-
让我们在聊天中继续讨论。
alloca()特别危险于malloc()的地方是典型操作系统的内核 - 内核,其固定大小的堆栈空间硬编码到其标头之一;它不像应用程序的堆栈那样灵活。以不合理的大小调用alloca()可能会导致内核崩溃。
某些编译器警告在编译内核代码时应该打开的某些选项下使用alloca()(甚至是VGA) - 这里,最好在堆中分配内存,而不是由硬件修复编码限制。
-
alloca()并不比int foo[bar];更危险,其中bar是某个任意整数。
如果你不小心写了超出用alloca分配的块(例如由于缓冲区溢出),那么你将覆盖函数的返回地址,因为它位于堆栈的"上方",即在你分配的块之后。
这样做的结果是双重的:
程序将崩溃,并且无法分辨它崩溃的原因或位置(由于覆盖的帧指针,堆栈很可能会放松到随机地址)。
它使缓冲区溢出的危险性增加了许多倍,因为恶意用户可以创建一个特殊的有效负载,这些有效负载将放在堆栈中,因此最终可以执行。
相反,如果你在堆上写一个块之外,你"只是"得到堆损坏。该程序可能会意外终止,但会正确展开堆栈,从而减少恶意代码执行的可能性。
-
在这种情况下,没有什么能比缓冲区溢出固定大小的堆栈分配缓冲区的危险大不相同。这种危险并非alloca所特有。
-
当然不是。但请检查原始问题。问题是:与malloc相比,alloca的危险是什么(因此堆栈上没有固定大小的缓冲区)。
alloca的一个缺陷是longjmp将其倒回。
也就是说,如果使用setjmp保存上下文,然后alloca某个内存,然后longjmp保存到上下文,则可能会丢失alloca内存(没有任何通知)。堆栈指针返回原处,因此不再保留内存;如果你调用一个函数或做另一个alloca,你将破坏原来的alloca。
为了澄清,我在这里具体指的是longjmp不会退出alloca发生的函数的情况!相反,函数用setjmp保存上下文;然后用alloca分配内存,最后一个longjmp发生在那个上下文中。该函数的alloca内存并非全部释放;它自setjmp以来分配的所有内存。当然,我说的是观察到的行为;我所知道的任何alloca都没有记录此类要求。
文档中的重点通常是alloca内存与函数激活相关联的概念,而不是任何块;多次调用alloca只是获取更多的堆栈内存,这些内存在函数终止时全部释放。不是这样;内存实际上与过程上下文相关联。当使用longjmp恢复上下文时,先前的alloca状态也是如此。这是堆栈指针寄存器本身用于分配的结果,也是(必然)在jmp_buf中保存和恢复的结果。
顺便说一下,如果它以这种方式工作,它提供了一种似乎合理的机制,用于故意释放用alloca分配的内存。
我遇到过这个问题是导致bug的根本原因。
-
这就是它应该做的事情 - longjmp回过头来让程序忘记堆栈中发生的一切:所有变量,函数调用等。alloca就像堆栈上的数组一样,所以预计它们会像堆叠中的其他东西一样被破坏。
-
man alloca给出了以下句子:"因为alloca()分配的空间是在堆栈帧中分配的,所以如果通过调用longjmp(3)或siglongjmp(3)跳过函数返回,则会自动释放该空间。"。因此,记录了alloca分配的内存在longjmp之后被破坏。
-
@tehftw描述的情况是在没有函数返回被longjmp跳过的情况下发生的。目标函数尚未返回。它已完成setjmp,alloca,然后longjmp。 longjmp可以将alloca状态回退到setjmp时的状态。也就是说,由alloca移动的指针遇到与未标记为volatile的局部变量相同的问题!
-
我不明白为什么会出乎意料。当setjmp然后alloca,然后longjmp时,alloca将被重新启动是正常的。 longjmp的重点是回到setjmp保存的状态!
-
@tehftw我从未见过记录这种特殊的互动。因此,除了通过编译器的实证调查之外,它不能依赖于任何一种方式。
-
man alloca记录了这种互动。如果我将alloca与longjmp一起使用,我个人会依赖于这种交互,因为它已被记录。你读了什么文件的alloca没有记录在那里?
-
@tehftw"Linux程序员手册"手册页;关于__builtin_alloca()(alloca的基础)的GCC文档,Microsoft的alloca上的MSDN; Solaris 11手册页; FreeBSD手册页。只有Linux提到了alloca和longjmp之间的交互;但只有当函数返回被longjmp跳过时释放的内存。
-
有趣的是,MSDN没有那个,因为我想询问Micrisoft是否是那些未能记录它的人:P我很惊讶其他人没有明确说出来。从我读到的,似乎GCC,FreeBSD和MSDN假设"恢复堆栈环境"足够清楚,alloca之后的内存将在longjmp之前被遗忘。
我认为没有人提到这一点:在函数中使用alloca会阻碍或禁用一些可能在函数中应用的优化,因为编译器无法知道函数堆栈帧的大小。
例如,C编译器的一个常见优化是消除在函数内使用帧指针,而是相对于堆栈指针进行帧访问;所以还有一个寄存器供一般使用。但是如果在函数内调用alloca,则部分函数的sp和fp之间的差异将是未知的,因此无法进行此优化。
鉴于其使用的罕见性以及作为标准函数的阴暗状态,编译器设计者很可能禁用任何可能导致alloca出现问题的优化,如果需要花费更多的努力才能使其与alloca一起使用。
更新:
由于可变长度的本地数组已添加到C和C ++中,并且由于这些数组向编译器提供与alloca非常相似的代码生成问题,因此我发现"使用稀有和阴暗状态"不适用于底层机制;但我仍然怀疑使用alloca或VLA往往会破坏使用它们的函数中的代码生成。我欢迎来自编译器设计者的任何反馈。
可悲的是,几乎令人敬畏的tcc中缺少真正令人敬畏的alloca()。 Gcc确实有alloca()。
它播下了自己毁灭的种子。以返回为析构函数。
像malloc()一样,它会在失败时返回一个无效指针,这将在具有MMU的现代系统上进行段错误(并希望重新启动那些没有)。
与自动变量不同,您可以在运行时指定大小。
它适用于递归。您可以使用静态变量来实现类似尾递归的操作,并且只使用其他几个传递信息到每次迭代。
如果推得太深,你就可以确定是否存在段错(如果你有MMU)。
请注意,当系统内存不足时,malloc()不再提供,因为它返回NULL(如果已分配,也会发出段错误)。即所有你能做的就是保释,或者只是尝试以任何方式分配它。
要使用malloc(),我使用全局变量并将它们指定为NULL。如果指针不是NULL,我在使用malloc()之前释放它。
如果要复制任何现有数据,也可以使用realloc()作为一般情况。如果要在realloc()之后复制或连接,则需要先检查指针。
3.2.5.2 alloca的优点
-
实际上alloca规范并没有说它在失败时返回一个无效指针(堆栈溢出)它说它有未定义的行为......而对于malloc它说它返回NULL,而不是一个随机的无效指针(OK,Linux乐观内存实现使得无用)。
-
@kriss Linux可能会杀死你的进程,但至少它不冒险进入未定义的行为
-
@ craig65535:表达式未定义行为通常表示该行为未由C或C ++规范定义。在任何给定的OS或编译器上都不会随机或不稳定。因此,将UB与"Linux"或"Windows"等操作系统的名称相关联毫无意义。它与它无关。
-
我试图说malloc返回NULL,或者在Linux的情况下,内存访问杀死你的进程,优于alloca的未定义行为。我想我一定误读了你的第一条评论。
不是很漂亮,但如果性能真的很重要,你可以预先在堆栈上分配一些空间。
如果您现在已经是需要的内存块的最大大小,并且您想要保持溢出检查,您可以执行以下操作:
1 2 3 4 5 6 7
| void f()
{
char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
SomeType *p = (SomeType *)array;
(...)
} |
-
是否保证char数组正确对齐任何数据类型? alloca提供了这样的承诺。
-
@Juhostman:如果你有问题,你可以使用struct(或任何类型)的数组而不是char。
-
这称为可变长度数组。它支持C90及更高版本,但不支持C ++。请参阅我可以在C ++ 03和C ++ 11中使用C可变长度数组吗?
实际上,alloca不保证使用堆栈。
实际上,alloca的gcc-2.95实现使用malloc本身从堆中分配内存。此外,该实现是错误的,它可能会导致内存泄漏和一些意外的行为,如果你在一个块内调用它进一步使用goto。不是说,你永远不应该使用它,但有时候,alloca导致比释放更多的开销。
-
听起来好像gcc-2.95破坏了alloca,可能无法安全地用于需要alloca的程序。当longjmp用于放弃执行alloca的帧时,它将如何清理内存?什么时候有人会使用gcc 2.95?
进程只有有限的可用堆栈空间 - 远小于malloc()可用的内存量。
通过使用alloca(),您可以大大增加获得Stack Overflow错误的机会(如果您很幸运,或者如果您不幸,则会出现无法解释的崩溃)。
alloca功能很棒,并且所有反对者都在简单地传播FUD。
1 2 3 4 5 6
| void foo()
{
int x = 50000;
char array[x];
char *parray = (char *)alloca(x);
} |
数组和parray完全相同,风险相同。说一个比另一个更好是语法选择,而不是技术选择。
至于选择堆栈变量与堆变量,对于具有范围内生命周期的变量,使用堆栈堆栈的长运行程序有很多优点。您可以避免堆碎片,并且可以避免使用未使用的(不可用的)堆空间来增加进程空间。你不需要清理它。您可以控制进程的堆栈分配。
为什么这么糟糕?
恕我直言,alloca被认为是不好的做法,因为每个人都害怕耗尽堆栈大小限制。
通过阅读这个帖子和其他一些链接我学到了很多东西:
-
https://unix.stackexchange.com/questions/63742/what-is-automatic-stack-expansion
-
Linux 32位计算机上程序的堆栈分配限制
-
ulimit -s
我使用alloca主要是为了使我的普通C文件可以在msvc和gcc上编译而不做任何改动,C89风格,没有#ifdef _MSC_VER等。
谢谢 !这个帖子让我注册到这个网站:)
-
请记住,在这个网站上没有"线程"这样的东西。 Stack Overflow具有问答格式,而不是讨论线程格式。"答案"与讨论论坛中的"回复"不同;这意味着您实际上是在提供问题的答案,不应该用于回答其他答案或对该主题发表评论。一旦你有至少50个代表,你可以发表评论,但一定要阅读"我什么时候不应该发表评论?"部分。请阅读"关于"页面以更好地了解网站的格式。
在我看来,alloca(),如果可用,应该只能以一种约束的方式使用。非常像使用"goto",相当多的其他合理的人不仅对alloca()的使用有强烈的厌恶,而且还存在。
对于嵌入式使用,堆栈大小已知并且可以通过对分配大小的约定和分析强加限制,以及编译器无法升级到支持C99 +的地方,使用alloca()很好,我一直已知使用它。
当可用时,VLA可能比alloca()具有一些优势:编译器可以生成堆栈限制检查,在使用数组样式访问时捕获越界访问(我不知道是否有任何编译器执行此操作,但它可以可以完成),并且代码分析可以确定数组访问表达式是否正确有界。请注意,在某些编程环境中,例如汽车,医疗设备和航空电子设备,即使对于固定大小的阵列,也必须进行此分析,包括自动(在堆栈上)和静态分配(全局或本地)。
在堆栈上存储数据和返回地址/帧指针的架构(据我所知,这就是所有这些),任何堆栈分配的变量都可能是危险的,因为可以获取变量的地址,并且未经检查的输入值可能允许各种各样的恶作剧。
在嵌入式空间中,可移植性不是一个问题,但是在精心控制的情况之外,它是反对使用alloca()的一个很好的论据。
在嵌入空间之外,我使用alloca()主要是在日志记录和格式化函数内部以提高效率,并且在非递归词法扫描器中使用临时结构(使用alloca()分配在标记化和分类期间创建,然后持久化在函数返回之前填充对象(通过malloc()分配)。对于较小的临时结构,使用alloca()可以在分配持久对象时大大减少碎片。
我不认为有人提到这一点,但alloca也有一些严重的安全问题,不一定与malloc一起出现(尽管这些问题也出现在任何基于堆栈的数组中,无论是否动态)。由于内存是在堆栈上分配的,因此缓冲区溢出/下溢比仅使用malloc会产生更严重的后果。
特别是,函数的返回地址存储在堆栈中。如果此值被破坏,您的代码可以转到任何可执行的内存区域。编译器竭尽全力使其变得困难(特别是通过随机化地址布局)。然而,这显然比堆栈溢出更糟,因为如果返回值被破坏,最好的情况是SEGFAULT,但它也可能开始执行随机内存,或者在最坏的情况下某些内存区域会损害程序的安全性。
这里的大多数答案都很难忽略这一点:使用_alloca()可能比仅仅在堆栈中存储大型对象更糟糕。
自动存储和_alloca()之间的主要区别在于后者存在额外的(严重)问题:分配的块不受编译器控制,因此编译器无法优化或回收它。
相比:
1 2 3 4
| while (condition) {
char buffer[0x100]; // Chill.
/* ... */
} |
有:
1 2 3 4
| while (condition) {
char* buffer = _alloca(0x100); // Bad!
/* ... */
} |
后者的问题应该是显而易见的。
-
你有没有任何实际的例子来证明VLA和alloca之间的区别(是的,我说的是VLA,因为alloca不仅仅是静态大小数组的创建者)?
-
第二个用例有用例,第一个用例不支持。在循环运行'n'次之后我可能想要'n'记录 - 可能在链表或树中;然后在函数最终返回时处理该数据结构。这并不是说我会以任何方式编码:-)
-
我会说"编译器无法控制它"是因为这是alloca()的定义方式;现代编译器知道什么是alloca,并特别对待它;它不仅仅是像80年代那样的库函数。 C99 VLA基本上是带有块范围的alloca(和更好的输入)。没有或多或少的控制,只是符合不同的语义。
-
@greggo:如果你是downvoter,我很乐意听你为什么认为我的回答没用。
-
没有downvote。不过上面的评论