Vai*_*hav 381 c malloc stack allocation alloca
alloca()
在堆栈上而不是在堆上分配内存,如同的情况一样malloc()
.所以,当我从例程返回时,内存被释放.所以,实际上这解决了我释放动态分配内存的问题.释放分配的内存malloc()
是一个令人头痛的问题,如果不知何故错过会导致各种内存问题.
alloca()
尽管有上述特征,为什么不鼓励使用?
Sea*_*ght 228
答案就在man
页面中(至少在Linux上):
返回值alloca()函数返回指向已分配空间开头的指针.如果分配导致堆栈溢出,则程序行为未定义.
这并不是说永远不应该使用它.我工作的其中一个OSS项目广泛使用它,只要你不滥用它(alloca
"巨大的价值"),它就没问题了.一旦你超过"几百字节"标记,就可以使用malloc
和朋友一起了.你可能仍然会遇到分配失败,但至少你会有一些失败的迹象,而不是只是吹掉堆栈.
Igo*_*aka 198
我遇到的最令人难忘的错误之一就是使用了内联函数alloca
.它表现为堆栈溢出(因为它在堆栈上分配)在程序执行的随机点.
在头文件中:
void DoSomething() {
wchar_t* pStr = alloca(100);
//......
}
Run Code Online (Sandbox Code Playgroud)
在实现文件中:
void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
}
Run Code Online (Sandbox Code Playgroud)
所以发生的事情是编译器内联DoSomething
函数和所有堆栈分配都发生在Process()
函数内部,从而引发了堆栈.在我的辩护中(我不是那个发现问题的人,我不得不去找其中一个高级开发人员,当我无法修复它时),它不是直的alloca
,它是ATL字符串转换之一宏.
所以教训是 - 不要alloca
在你认为可能被内联的函数中使用.
Pat*_*ter 72
老问题,但没有人提到它应该被可变长度数组所取代.
char arr[size];
Run Code Online (Sandbox Code Playgroud)
代替
char *arr=alloca(size);
Run Code Online (Sandbox Code Playgroud)
它在标准C99中,在许多编译器中作为编译器扩展存在.
Art*_*ldt 57
如果你不能使用标准的局部变量,alloca()是非常有用的,因为它的大小需要在运行时确定,并且你绝对可以 保证在这个函数返回后不会使用你从alloca()获得的指针.
如果你,你可以相当安全
真正的危险来自于其他人稍后会违反这些条件的可能性.考虑到这一点,将缓冲区传递给将文本格式化为的函数是很好的:)
Fre*_*ory 40
正如本新闻组的帖子所述,有几个原因alloca
可以说使用困难和危险:
alloca
.alloca
不同的方式解释预期的行为,因此即使在支持它的编译器之间也无法保证可移植性.Ton*_*roy 20
仍然没有使用alloca,为什么?
我没有看到这样的共识.很多强大的专业人士; 一些缺点:
while
或for
循环)或多个范围中使用时,内存会在每次迭代/范围内累积,并且在函数退出之前不会释放:这与在控制结构范围内定义的正常变量形成对比(例如,for {int i = 0; i < 2; ++i) { X }
将累积alloca
在X请求的内存,但每次迭代将回收固定大小数组的内存.inline
调用函数alloca
,但是如果强制它们alloca
则会在调用者的上下文中发生(即在调用者返回之前不会释放堆栈)alloca
从非便携式功能/黑客转变为标准化扩展,但一些负面看法可能会持续存在malloc
明确的控制malloc
鼓励考虑释放 - 如果通过包装函数(例如WonderfulObject_DestructorFree(ptr)
)管理,那么该函数提供实现清理操作的点(如关闭文件描述符,释放内部指针或执行一些日志记录),而无需对客户端进行显式更改代码:有时它是一个很好的模型,采用一致
WonderfulObject* p = WonderfulObject_AllocConstructor();
- 当"构造函数"是一个函数返回malloc
内存时(因为在函数返回要存储的值之后内存保持分配p
),这是可能的,但不是如果"构造函数"使用alloca
WonderfulObject_AllocConstructor
可以实现这一点,但"宏是邪恶的",因为它们可以相互冲突和非宏代码,并产生意外的替换和随之而来的难以诊断的问题free
ValGrind,Purify等可以检测到缺失的操作,但是根本无法检测到缺少"析构函数"的调用 - 在执行预期用法方面的一个非常微弱的好处; 某些alloca()
实现(例如GCC)使用内联宏alloca()
,因此无法以malloc
/ realloc
/ free
(例如电围栏)的方式对内存使用诊断库进行运行时替换在许多系统中,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
}
}
Run Code Online (Sandbox Code Playgroud)
JSB*_*ոգչ 13
所有其他答案都是正确的.但是,如果您想要使用的东西alloca()
相当小,我认为这是一种比使用malloc()
或其他更快更方便的好技术.
换句话说,alloca( 0x00ffffff )
是危险的,并且可能导致溢出,完全一样多char hugeArray[ 0x00ffffff ];
.要小心谨慎,你会没事的.
Sil*_*rge 11
每个人都已经指出了堆栈溢出中潜在的未定义行为这个大问题,但是我应该提到Windows环境有一个很好的机制来使用结构化异常(SEH)和保护页面来捕获它.由于堆栈仅根据需要增长,因此这些保护页面位于未分配的区域中.如果分配给它们(通过溢出堆栈),则抛出异常.
您可以捕获此SEH异常并调用_resetstkoflw重置堆栈并继续您的快乐方式.这不是理想的,但它是另一种机制,至少知道当东西击中粉丝时出现问题.*nix可能有类似我不知道的东西.
我建议通过包装alloca并在内部跟踪来限制最大分配大小.如果你真的是硬核的话,你可以在你的函数顶部抛出一些范围的哨兵来跟踪函数范围内的任何alloca分配,并且理智地检查你的项目所允许的最大数量.
此外,除了不允许内存泄漏之外,alloca不会导致内存碎片,这非常重要.如果你聪明地使用它,我不认为alloca是不好的做法,这基本上适用于所有事情.:-)
pho*_*ger 11
这个"旧"问题有很多有趣的答案,甚至是一些相对较新的答案,但我没有发现任何提及这个问题....
如果使用得当且小心谨慎,一致地使用
alloca()
(可能是应用程序范围的)处理小的可变长度分配(或C99 VLA,如果可用)可以导致整体堆栈增长低于使用超大固定长度本地阵列的等效实现.所以,alloca()
可能是好你的筹码,如果你仔细地使用它.
我发现引用....好吧,我引用了这个引用.但是真的,想一想......
@j_random_hacker在其他答案的评论中是非常正确的:避免使用alloca()
支持超大的本地数组不会使程序更安全地从堆栈溢出(除非你的编译器足够大,允许内联函数,alloca()
在这种情况下你应该使用升级,或者除非你使用alloca()
内部循环,在这种情况下你应该...不使用alloca()
内部循环).
我曾在桌面/服务器环境和嵌入式系统上工作过.许多嵌入式系统根本不使用堆(它们甚至不支持它),原因包括认为动态分配的内存是邪恶的,因为应用程序上存在内存泄漏的风险多次重启多年,或动态内存危险的更合理的理由,因为无法确定应用程序永远不会将其堆碎到虚假内存耗尽点.因此嵌入式程序员几乎没有其他选择.
alloca()
(或VLA)可能只是工作的正确工具.我已经一次又一次地看到程序员将堆栈分配的缓冲区"大到足以处理任何可能的情况".在深度嵌套的调用树中,重复使用该(反 - ?)模式会导致堆栈使用过度.(想象一下20级深度的调用树,在每个级别出于不同的原因,该函数盲目地过度分配1024字节的缓冲区"只是为了安全",而通常它只使用16或更少,并且只在非常极少数情况下可能会使用更多.)另一种方法是使用alloca()
或使用VLA并仅分配与您的功能需求一样多的堆栈空间,以避免不必要地增加堆栈负担.希望当调用树中的一个函数需要大于正常的分配时,调用树中的其他函数仍然使用它们的正常小分配,并且整个应用程序堆栈的使用量明显少于每个函数盲目地过度分配本地缓冲区的情况. .
alloca()
......基于此页面上的其他答案,似乎VLA应该是安全的(如果在循环内调用它们不会复合堆栈分配),但是如果您正在使用alloca()
,请注意不要在循环内使用它,并使如果有可能在另一个函数的循环中调用它,那么确保你的函数不能被内联.
kri*_*iss 10
alloca()既美观又高效......但它也深受打击.
在大多数情况下,您可以使用局部变量和majorant大小替换它.如果它用于大型对象,将它们放在堆上通常是一个更安全的想法.
如果你真的需要它,你可以使用VLA(在C++中没有vla,太糟糕了).它们比alloca()在范围行为和一致性方面要好得多.正如我所看到的,VLA是一种正确的alloca().
当然,使用所需空间的主要部分的本地结构或数组仍然更好,如果你没有这样的majorant堆分配使用普通的malloc()可能是理智的.我看到没有理智的用例,你真的需要alloca()或VLA.
原因如下:
char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;
Run Code Online (Sandbox Code Playgroud)
并不是说有人会写这个代码,但是你传递给的大小参数alloca
几乎肯定来自某种输入,这可能会恶意地使你的程序达到alloca
类似的程度.毕竟,如果大小不是基于输入或者没有可能变大,为什么不直接声明一个小的,固定大小的本地缓冲区?
实际上所有使用alloca
和/或C99 vlas的代码都有严重的错误,这些错误会导致崩溃(如果你很幸运)或特权妥协(如果你不是那么幸运).
小智 7
一个alloca()
比malloc()
内核特别危险的地方- 典型操作系统的内核有一个固定大小的堆栈空间硬编码到其标题之一; 它不像应用程序的堆栈那样灵活.alloca()
以不合理的大小进行调用可能会导致内核崩溃.某些编译器警告alloca()
在编译内核代码时应该打开的某些选项下使用(甚至是VGA) - 在这里,最好在堆中分配不受硬编码限制修复的内存.
如果您不小心写了超出分配的块alloca
(例如,由于缓冲区溢出),那么您将覆盖函数的返回地址,因为该地址位于堆栈的“上方”,即在分配的块之后。
其结果有两个方面:
该程序将崩溃,并且无法告知崩溃原因或原因(由于覆盖了帧指针,堆栈很可能会退回到随机地址)。
由于恶意用户可以制作一个特殊的有效负载,该缓冲区将被放入堆栈中并最终被执行,因此它使缓冲区溢出变得更加危险得多。
相反,如果您在堆上写的超出块,则“只是”得到堆损坏。该程序可能会意外终止,但会正确展开堆栈,从而减少了恶意代码执行的机会。
我认为没有人提到过这一点:在函数中使用alloca会阻止或禁用某些本可以应用在函数中的优化,因为编译器无法知道函数堆栈帧的大小。
例如,C编译器的常见优化是消除在函数中使用帧指针,而是相对于堆栈指针进行帧访问;因此,还有一个通用寄存器。但是,如果在函数内调用alloca,则部分函数无法知道sp和fp之间的差异,因此无法进行此优化。
考虑到其使用的稀有性以及其作为标准函数的不安全状态,编译器设计人员很可能会禁用可能对alloca造成麻烦的任何优化,如果要花费更多的精力才能使其与alloca一起使用。
更新: 由于可变长度局部数组已添加到C中,并且由于它们在分配程序中与alloca呈现出非常相似的代码生成问题,因此我看到“使用稀有性和不可靠状态”不适用于底层机制;但是我仍然怀疑使用alloca或VLA倾向于损害使用它们的函数中的代码生成。我欢迎编译器设计师的任何反馈。
进程只有有限的可用堆栈空间——远小于malloc()
.
通过使用,alloca()
您会显着增加出现 Stack Overflow 错误的机会(如果幸运的话,或者如果不是,则发生莫名其妙的崩溃)。
小智 5
可悲的alloca()
是,几乎令人敬畏的 tcc 缺少真正令人敬畏的东西。Gcc 确实有alloca()
.
它播下了自己毁灭的种子。以 return 作为析构函数。
就像malloc()
它在失败时返回一个无效的指针一样,这将在带有 MMU 的现代系统上出现段错误(并希望重新启动那些没有的)。
与自动变量不同,您可以在运行时指定大小。
它适用于递归。您可以使用静态变量来实现类似于尾递归的效果,并仅使用其他几个将信息传递给每次迭代。
如果你推得太深,你肯定会出现段错误(如果你有一个 MMU)。
请注意,malloc()
当系统内存不足时,它不会提供更多信息,因为它返回 NULL(如果已分配,也会出现段错误)。即您所能做的就是保释或尝试以任何方式分配它。
要使用,malloc()
我使用全局变量并将它们分配为 NULL。如果指针不是 NULL 我在使用之前释放它malloc()
。
realloc()
如果要复制任何现有数据,您也可以将其用作一般情况。您需要先检查指针才能确定是否要在realloc()
.
倒退的一个陷阱alloca
是longjmp
。
也就是说,如果使用保存上下文setjmp
,则需要alloca
一些内存,然后再保存到上下文,则longjmp
可能会丢失该alloca
内存(没有任何通知)。堆栈指针又回到了原来的位置,因此不再保留内存。如果您调用一个函数或执行另一个函数alloca
,则会破坏原始函数alloca
。
为了澄清起见,我在这里具体指的是一种情况,其中这种情况longjmp
不会从发生功能的alloca
地方返回!而是,函数使用来保存上下文setjmp
;然后使用分配内存,alloca
最后对该上下文进行一次longjmp。该函数的alloca
内存并没有全部释放。只是自以来分配的所有内存setjmp
。当然,我说的是一种观察到的行为。alloca
我所知道的任何此类要求都没有记录在案。
文档中的焦点通常集中在以下概念上:alloca
内存与功能激活关联,而不与任何块关联;alloca
该函数的多次调用仅会占用更多的堆栈内存,而这些内存将在函数终止时释放。不是这样;内存实际上与过程上下文相关联。使用还原上下文后longjmp
,先前alloca
状态也将恢复。这是因为堆栈指针寄存器本身被用于分配,并且(有必要)保存和恢复到jmp_buf
。
顺便说一句,如果以此方式工作,它提供了一种合理的机制来故意释放用分配的内存alloca
。
我已经将其作为错误的根本原因。