为什么使用alloca()不被视为良好做法?

Vai*_*hav 381 c malloc stack allocation alloca

alloca()在堆栈上而不是在堆上分配内存,如同的情况一样malloc().所以,当我从例程返回时,内存被释放.所以,实际上这解决了我释放动态分配内存的问题.释放分配的内存malloc()是一个令人头痛的问题,如果不知何故错过会导致各种内存问题.

alloca()尽管有上述特征,为什么不鼓励使用?

Sea*_*ght 228

答案就在man页面中(至少在Linux上):

返回值alloca()函数返回指向已分配空间开头的指针.如果分配导致堆栈溢出,则程序行为未定义.

这并不是说永远不应该使用它.我工作的其中一个OSS项目广泛使用它,只要你不滥用它(alloca"巨大的价值"),它就没问题了.一旦你超过"几百字节"标记,就可以使用malloc和朋友一起了.你可能仍然会遇到分配失败,但至少你会有一些失败的迹象,而不是只是吹掉堆栈.

  • @Sean:是的,堆栈溢出风险是给出的原因,但这个原因有点傻.首先是因为(正如Vaibhav所说)大型局部阵列导致完全相同的问题,但几乎不会被诋毁.此外,递归可以很容易地吹掉堆栈.对不起,但是我希望你能够反驳一下流行的观点,即手册页中给出的理由是合理的. (85认同)
  • 我的观点是,手册页中给出的理由没有意义,因为alloca()与被认为是犹太教的其他东西(局部数组或递归函数)完全一样"坏". (46认同)
  • @ninjalj:不是经验丰富的C/C++程序员,但我确实认为许多担心`alloca()`的人对本地数组或递归没有同样的恐惧(事实上很多人会大喊"alloca()"会赞美递归,因为它"看起来很优雅").我同意Shaun的建议("alloca()适用于小额分配")但我不同意将alloca()框架为3中唯一邪恶的心态 - 它们同样危险! (37认同)
  • 注意:鉴于Linux的"乐观"内存分配策略,您很可能*不会*得到堆耗尽失败的任何迹象...而malloc()将返回一个很好的非NULL指针,然后当你尝试要实际访问它指向的地址空间,你的进程(或其他一些进程,不可预测的)将被OOM杀手杀死.当然这是Linux的"特性",而不是C/C++问题本身,但在辩论alloca()或malloc()是否"更安全"时要记住这一点.:) (33认同)
  • 那么你真的没有问题,你也不会有声明大数组? (30认同)
  • @singpolyma - 以下崩溃对我来说,所以我不确定你的意思:int main(int argc,char**argv){char byebye [1024*1024*10];/*10MB数组*/; byebye [0] = 0; 返回0; } (17认同)
  • +1否定@ j_random_hacker的投票结果.手册是完全合理的,说明如果`alloca()`打击堆栈将会发生什么(事实上,不解释这一点是不负责任的).Sean周围的解释非常合理,即使是对答案进行限定,也不会被解释为对`alloca()`的彻底禁止.肖恩的回答中没有任何内容暗示了j_random_hacker所说的心态.实际上,肖恩的前两条评论比j_random_hacker的第一条评论提前几个小时发布,非常清楚地表明这不是肖恩的观点. (14认同)
  • @sean我理解这一点,如果我分配了太多的内存(因为堆栈空间与堆相比有限),这会导致溢出; 但同样是本地数组的情况.如果我保持在我的极限之内那么我应该没事,对吧?然后我还看到反alloa评论. (8认同)
  • 我要补充的是,不幸的是,当 alloca 不是错误的选择时,这个答案并没有指定 alloca 的最大优势:避免删除/释放开销。我通过使用 alloca 使函数速度提高了 100 倍以上,这仅仅是因为我删除了堆内存管理开销。 (5认同)
  • @j_random_hacker:你真的认为大型本地阵列被认为是犹太人吗? (4认同)
  • `alloca`可以用于小分配,或者如果你的函数是叶函数(或非常接近叶子函数).这样,您可以在上面设置一个上限,以便从那里使用多少堆栈空间.示例:如果您有一个需要将字符串从ASCII转换为Unicode(或反之亦然)的日志记录功能,则使用`alloca`而不是`malloc`作为转换缓冲区会更有效. (4认同)
  • 我不完全确定你在做什么...不要使用大型本地阵列?应该经常使用未定义行为失败的函数?文档不可信任? (3认同)
  • @j_random_hacker:这真的很简单。您可以在没有 alloca 的情况下在堆栈上进行固定大小的分配,或者使用 alloca 进行动态大小的分配。在某种程度上你是对的——在任何一种情况下,你都必须考虑到堆栈。 (2认同)
  • “此外,递归也很容易破坏堆栈。”许多现代系统都有堆栈保护页,用于自动增长堆栈或至少产生错误。递归通常会导致保护页面被命中,而单个大分配可以直接跳过它。 (2认同)

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在你认为可能被内联的函数中使用.

  • 你吸烟的是什么编译器? (123认同)
  • 有趣.但这不会成为编译器错误吗?毕竟,内联改变了代码的行为(它延迟了使用alloca分配的空间的释放). (80认同)
  • 显然,至少GCC会考虑到这一点:"请注意,函数定义中的某些用法可能使其不适合内联替换.其中包括:使用varargs,使用alloca,[...]".http://gcc.gnu.org/onlinedocs/gcc/Inline.html (54认同)
  • 我不明白的是为什么编译器没有充分利用范围来确定子范围内的allocas或多或少"被释放":堆栈指针在进入范围之前可以回到它的位置,就像在什么时候做的那样从函数返回(不是吗?) (22认同)
  • 抱歉,我正在编译调试模式!VS2010给出:警告C4750:'bool __cdecl inlined_TestW32Mem5(void)':函数_alloca()内联到循环中 (6认同)
  • 我已经投了很多,但答案写得很好:我同意其他人你是错误的,因为这是一个明显的**编译器错误**.编译器在不应该进行的优化中做出错误的假设.解决编译器错误是很好的,但我不会因为它而对编译器有任何错误. (6认同)
  • Microsoft VC++ 6. (3认同)
  • 那就是你_forceinline(如果它有alloca,那么函数没有内联) (3认同)
  • 更糟糕的是:一位同事设法在一段时间(!完成)循环中直接使用alloca ... (3认同)
  • 本课并不意味着不应该使用`alloca()`.它只是意味着您不应该允许编译器内联它.GCC和Clang在任何调用`alloca()`的函数上都说`__attribute __((__ noinline __))`,这很容易避免.问题解决了. (3认同)
  • @EvanCarroll 这不一定是编译器错误,因为“alloca”不是标准化函数。要解决该编译器的问题,供应商所要做的就是将其添加到文档中。“此编译器的函数内联逻辑忽略了`alloca`。`alloca`内存仅由非内联函数的返回来处置”。这并不违反任何主要标准;不是 ISO C 或 POSIX,只是其他实现的一些“事实上的”行为。 (2认同)

Pat*_*ter 72

老问题,但没有人提到它应该被可变长度数组所取代.

char arr[size];
Run Code Online (Sandbox Code Playgroud)

代替

char *arr=alloca(size);
Run Code Online (Sandbox Code Playgroud)

它在标准C99中,在许多编译器中作为编译器扩展存在.

  • 不幸的是,有一些编译器(Keil for ARM)通过默默地将 malloc() 链接到代码中来在堆上实现 VLA。这是一个很大的烦恼,因为你永远不知道内存分配在哪里,并且我不知道之后是否正确调用 free() 。 (6认同)
  • Jonathan Leffler在评论Arthur Ulfeldt的回答时提到了这一点. (5认同)
  • 一个注意事项 - 那些是可变长度数组,而不是动态数组.后者是可调整大小的,通常在堆上实现. (5认同)
  • 确实,但它也表明它很容易被错过,因为尽管在发布之前阅读了所有回复,但我还没有看到它。 (4认同)
  • Linus Torvalds 不喜欢 [Linux 内核中的 VLA](https://lkml.org/lkml/2018/3/7/621)。从 4.20 版开始,Linux 应该几乎没有 VLA。 (4认同)

Art*_*ldt 57

如果你不能使用标准的局部变量,alloca()是非常有用的,因为它的大小需要在运行时确定,并且你绝对可以 保证在这个函数返回后不会使用你从alloca()获得的指针.

如果你,你可以相当安全

  • 不要返回指针或包含它的任何内容.
  • 不要将指针存储在堆上分配的任何结构中
  • 不要让任何其他线程使用指针

真正的危险来自于其他人稍后会违反这些条件的可能性.考虑到这一点,将缓冲区传递给将文本格式化为的函数是很好的:)

  • C99的VLA(可变长度数组)功能支持动态大小的局部变量,而无需明确要求使用alloca(). (11认同)
  • 但这与处理指向局部变量的指针没有什么不同。他们也可以被愚弄...... (3认同)
  • NEATO!在http://www.programmersheaven.com/2/Pointers-and-Arrays-page-2的"3.4可变长度阵列"部分找到更多信息 (2认同)
  • @Jonathan Leffler你可以用alloca做一件事,但你不能用VLA使用restrict关键字.像这样:float*restrict heavy_used_arr = alloca(sizeof(float)*size); 而不是浮动heavy_used_arr [size].即使size是编译常量,它也可以帮助一些编译器(在我的例子中为gcc 4.8)来优化程序集.请参阅我的问题:http://stackoverflow.com/questions/19026643/using-restrict-with-arrays (2认同)

Fre*_*ory 40

正如本新闻组的帖子所述,有几个原因alloca可以说使用困难和危险:

  • 并非所有编译器都支持alloca.
  • 有些编译器会以alloca不同的方式解释预期的行为,因此即使在支持它的编译器之间也无法保证可移植性.
  • 一些实现是错误的.

  • 我在这个链接上看到的一件事就是这个页面上没有的东西是一个使用`alloca()`的函数需要单独的寄存器来保存堆栈指针和帧指针.在x86 CPU> = 386时,堆栈指针`ESP`可用于两者,释放`EBP` - 除非使用`alloca()`. (23认同)
  • 该页面的另一个好处是,除非编译器的代码生成器将其作为特殊情况处理,否则`f(42,alloca(10),43);`由于可能由于alloca()调整堆栈指针而可能崩溃`*after*至少有一个参数被推到它上面. (9认同)
  • 请记住,自1991年以来,*lot*已发生变化.所有现代C编译器(即使在2009年)都必须处理alloca作为特例; 它是一个内在的而不是一个普通的函数,甚至可能不会调用函数.因此,参数中的分配问题(从1970年代开始在K&R C中出现)现在应该不是问题.我在Tony D的回答中做出的评论中有更详细的说明 (5认同)
  • 链接的文章似乎是由约翰莱文写的 - 写了"连接器和装载器"的家伙,我会相信他所说的. (3认同)
  • 链接的帖子是John Levine发布的帖子. (3认同)
  • +1没有做出无关紧要的论点,比如更高的投票答案,忘记了他们的参数也可以应用于可变长度数组或任何堆栈耗尽的设备. (3认同)

Dav*_*ley 25

一个问题是它不是标准的,尽管它得到了广泛的支持.在其他条件相同的情况下,我总是使用标准函数而不是常见的编译器扩展.


Ton*_*roy 20

仍然没有使用alloca,为什么?

我没有看到这样的共识.很多强大的专业人士; 一些缺点:

  • C99提供可变长度数组,这些数组通常会优先使用,因为符号与固定长度数组更加一致并且直观整体
  • 许多系统可用于堆栈的总内存/地址空间少于堆,这使得程序稍微容易受到内存耗尽的影响(通过堆栈溢出):这可能被视为好事或坏事 - 一个堆栈没有按照堆的方式自动增长的原因是为了防止失控程序对整个机器产生同样多的负面影响
  • 当在更局部的范围(例如whilefor循环)或多个范围中使用时,内存会在每次迭代/范围内累积,并且在函数退出之前不会释放:这与在控制结构范围内定义的正常变量形成对比(例如,for {int i = 0; i < 2; ++i) { X }将累积alloca在X请求的内存,但每次迭代将回收固定大小数组的内存.
  • 现代编译器通常不inline调用函数alloca,但是如果强制它们alloca则会在调用者的上下文中发生(即在调用者返回之前不会释放堆栈)
  • 很久以前,alloca从非便携式功能/黑客转变为标准化扩展,但一些负面看法可能会持续存在
  • 生命周期与功能范围有关,这可能适合或不适合程序员,而不是malloc明确的控制
  • 必须使用malloc鼓励考虑释放 - 如果通过包装函数(例如WonderfulObject_DestructorFree(ptr))管理,那么该函数提供实现清理操作的点(如关闭文件描述符,释放内部指针或执行一些日志记录),而无需对客户端进行显式更改代码:有时它是一个很好的模型,采用一致
    • 在这种伪OO编程风格中,很自然地需要类似的东西WonderfulObject* p = WonderfulObject_AllocConstructor();- 当"构造函数"是一个函数返回malloc内存时(因为在函数返回要存储的值之后内存保持分配p),这是可能的,但不是如果"构造函数"使用alloca
      • 一个宏版本WonderfulObject_AllocConstructor可以实现这一点,但"宏是邪恶的",因为它们可以相互冲突和非宏代码,并产生意外的替换和随之而来的难以诊断的问题
    • freeValGrind,Purify等可以检测到缺失的操作,但是根本无法检测到缺少"析构函数"的调用 - 在执行预期用法方面的一个非常微弱的好处; 某些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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 关于这个例子,我通常会关注*需要*always_inline以避免内存损坏的事情.... (3认同)
  • 让我重新表述一下:这是一个非常好的答案。到目前为止,我认为您建议人们使用某种反模式。 (2认同)

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(),请注意不要在循环内使用它,并使如果有可能在另一个函数的循环中调用它,那么确保你的函数不能被内联.

  • 我同意这一点。`alloca()` 的危险确实存在,但是 `malloc()` 的内存泄漏也是如此(为什么不使用 GC?有人可能会争论)。小心使用时,“alloca()”对于减小堆栈大小非常有用。 (3认同)

kri*_*iss 10

alloca()既美观又高效......但它也深受打击.

  • 破坏范围行为(函数范围而不是块范围)
  • 使用与malloc不一致的(alloca()- ted指针不应该被释放,因此你必须跟踪你的指针来自free()只有你用malloc()得到的那些)
  • 当你也使用内联时,不良行为(范围有时会转到调用者函数,具体取决于被调用者是否内联).
  • 没有堆栈边界检查
  • 失败时的未定义行为(不像malloc那样返回NULL ......失败意味着什么,因为它不检查堆栈边界......)
  • 不是ansi标准

在大多数情况下,您可以使用局部变量和majorant大小替换它.如果它用于大型对象,将它们放在堆上通常是一个更安全的想法.

如果你真的需要它,你可以使用VLA(在C++中没有vla,太糟糕了).它们比alloca()在范围行为和一致性方面要好得多.正如我所看到的,VLA是一种正确的alloca().

当然,使用所需空间的主要部分的本地结构或数组仍然更好,如果你没有这样的majorant堆分配使用普通的malloc()可能是理智的.我看到没有理智的用例,你真的需要alloca()VLA.

  • 我希望alloca拥有一个相应的“ freea”,并有一个规范,即调用“ freea”将撤消创建对象的“ alloca”以及所有后续对象的影响,并且要求必须在功能中存储“ alloca”也可以在其中“释放”。这样一来,几乎所有的实现都可以以兼容的方式支持alloca / freea,可以减轻内联的问题,并且通常使事情变得更加整洁。 (2认同)
  • @supercat - 我也希望如此.出于这个原因(以及更多),我使用了一个抽象层(主要是宏和内联函数),因此我不会直接调用`alloca`或`malloc`或`free`.我说的是``stack | heap} _alloc_ {bytes,items,struct,varstruct}`和`{stack | heap} _dealloc`.所以,`heap_dealloc`只调用`free`而`stack_dealloc`就是no-op.这样,堆栈分配可以很容易地降级到堆分配,并且意图也更清晰. (2认同)

R..*_*R.. 9

原因如下:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;
Run Code Online (Sandbox Code Playgroud)

并不是说有人会写这个代码,但是你传递给的大小参数alloca几乎肯定来自某种输入,这可能会恶意地使你的程序达到alloca类似的程度.毕竟,如果大小不是基于输入或者没有可能变大,为什么不直接声明一个小的,固定大小的本地缓冲区?

实际上所有使用alloca和/或C99 vlas的代码都有严重的错误,这些错误会导致崩溃(如果你很幸运)或特权妥协(如果你不是那么幸运).

  • `*0 = 9;`无效C.至于测试传递给`alloca`的大小,测试它是什么?没有办法知道这个限制,如果你只是针对一个很小的固定已知安全大小(例如8k)进行测试,你也可以在堆栈上使用固定大小的数组. (7认同)
  • 知道什么也有危险吗?这个:`*0 = 9;`惊人!我想我永远不应该使用指针(或至少取消引用它们).呃,等等.我可以测试它是否为null.嗯.我想我也可以通过`alloca`来测试我想要分配的内存大小.奇怪的人.奇怪的. (6认同)
  • 你所知道的"你知道的大小是否足够小或者它是依赖于输入的,因此可能是任意大的"这一论点的问题,因为我认为它同样适用于递归.一个实际的妥协(对于这两种情况)是假设如果大小受到`small_constant*log(user_input)`的限制,那么我们可能有足够的内存. (6认同)

小智 7

一个alloca()malloc()内核特别危险的地方- 典型操作系统的内核有一个固定大小的堆栈空间硬编码到其标题之一; 它不像应用程序的堆栈那样灵活.alloca()以不合理的大小进行调用可能会导致内核崩溃.某些编译器警告alloca()在编译内核代码时应该打开的某些选项下使用(甚至是VGA) - 在这里,最好在堆中分配不受硬编码限制修复的内存.

  • `alloca()`并不比`int foo [bar]更危险;``bar`是一些任意整数. (6认同)
  • @ToddLehman 这是正确的,正是出于这个原因,我们已经在内核中禁止 VLA 几年了,并且自 2018 年以来一直没有 VLA :-) (2认同)

rus*_*tyx 6

如果您不小心写了超出分配的块alloca(例如,由于缓冲区溢出),那么您将覆盖函数的返回地址,因为该地址位于堆栈的“上方”,即分配的块之后。

_alloca堆栈上的块

其结果有两个方面:

  1. 该程序将崩溃,并且无法告知崩溃原因或原因(由于覆盖了帧指针,堆栈很可能会退回到随机地址)。

  2. 由于恶意用户可以制作一个特殊的有效负载,该缓冲区将被放入堆栈中并最终被执行,因此它使缓冲区溢出变得更加危险得多。

相反,如果您在堆上写的超出块,则“只是”得到堆损坏。该程序可能会意外终止,但会正确展开堆栈,从而减少了恶意代码执行的机会。

  • 在这种情况下,没有什么比缓冲区溢出固定大小的堆栈分配缓冲区的危险大不相同了。这种危险不是`alloca`独有的。 (9认同)
  • 当然不是。但是请检查原始问题。问题是:与malloc相比,alloca的危险是什么(因此堆栈上的缓冲区大小不是固定的)。 (2认同)

gre*_*ggo 6

我认为没有人提到过这一点:在函数中使用alloca会阻止或禁用某些本可以应用在函数中的优化,因为编译器无法知道函数堆栈帧的大小。

例如,C编译器的常见优化是消除在函数中使用帧指针,而是相对于堆栈指针进行帧访问;因此,还有一个通用寄存器。但是,如果在函数内调用alloca,则部分函数无法知道sp和fp之间的差异,因此无法进行此优化。

考虑到其使用的稀有性以及其作为标准函数的不安全状态,编译器设计人员很可能会禁用可能对alloca造成麻烦的任何优化,如果要花费更多的精力才能使其与alloca一起使用。

更新: 由于可变长度局部数组已添加到C中,并且由于它们在分配程序中与alloca呈现出非常相似的代码生成问题,因此我看到“使用稀有性和不可靠状态”不适用于底层机制;但是我仍然怀疑使用alloca或VLA倾向于损害使用它们的函数中的代码生成。我欢迎编译器设计师的任何反馈。

  • 如果 VLA 或 alloca 的替代方案是调用“malloc”和“free”,则该函数的使用可能会更有效,即使它需要成帧。 (4认同)
  • &gt; *我仍然怀疑使用 alloca 或 VLA 往往会损害代码生成* 我认为使用 alloca 需要帧指针,因为堆栈指针的移动方式在编译时并不明显。可以在循环中调用alloca以不断获取更多堆栈内存,或者使用运行时计算的大小等。如果有帧指针,则生成的代码对局部变量有稳定的引用,并且堆栈指针可以做任何它想做的事情;它没有被使用。 (2认同)

Ric*_*dle 5

进程只有有限的可用堆栈空间——远小于malloc().

通过使用,alloca()您会显着增加出现 Stack Overflow 错误的机会(如果幸运的话,或者如果不是,则发生莫名其妙的崩溃)。

  • 这在很大程度上取决于应用程序。对于内存有限的嵌入式应用程序来说,堆栈大小大于堆(如果甚至有堆)的情况并不罕见。 (3认同)

小智 5

可悲的alloca()是,几乎令人敬畏的 tcc 缺少真正令人敬畏的东西。Gcc 确实有alloca().

  1. 它播下了自己毁灭的种子。以 return 作为析构函数。

  2. 就像malloc()它在失败时返回一个无效的指针一样,这将在带有 MMU 的现代系统上出现段错误(并希望重新启动那些没有的)。

  3. 与自动变量不同,您可以在运行时指定大小。

它适用于递归。您可以使用静态变量来实现类似于尾递归的效果,并仅使用其他几个将信息传递给每次迭代。

如果你推得太深,你肯定会出现段错误(如果你有一个 MMU)。

请注意,malloc()当系统内存不足时,它不会提供更多信息,因为它返回 NULL(如果已分配,也会出现段错误)。即您所能做的就是保释或尝试以任何方式分配它。

要使用,malloc()我使用全局变量并将它们分配为 NULL。如果指针不是 NULL 我在使用之前释放它malloc()

realloc()如果要复制任何现有数据,您也可以将其用作一般情况。您需要先检查指针才能确定是否要在realloc().

3.2.5.2 alloca的优点

  • 实际上, alloca 规范并没有说它在失败(堆栈溢出)时返回一个无效指针,它说它有未定义的行为......而对于 malloc,它说它返回 NULL,而不是一个随机的无效指针(好吧,Linux 乐观内存实现使得无用)。 (5认同)

Kaz*_*Kaz 5

倒退的一个陷阱allocalongjmp

也就是说,如果使用保存上下文setjmp,则需要alloca一些内存,然后再保存到上下文,则longjmp可能会丢失该alloca内存(没有任何通知)。堆栈指针又回到了原来的位置,因此不再保留内存。如果您调用一个函数或执行另一个函数alloca,则会破坏原始函数alloca

为了澄清起见,我在这里具体指的是一种情况,其中这种情况longjmp不会从发生功能的alloca地方返回!而是,函数使用来保存上下文setjmp;然后使用分配内存,alloca最后对该上下文进行一次longjmp。该函数的alloca内存并没有全部释放。只是自以来分配的所有内存setjmp。当然,我说的是一种观察到的行为。alloca我所知道的任何此类要求都没有记录在案。

文档中的焦点通常集中在以下概念上:alloca内存与功能激活关联,而不与任何块关联;alloca该函数的多次调用仅会占用更多的堆栈内存,而这些内存将在函数终止时释放。不是这样;内存实际上与过程上下文相关联。使用还原上下文后longjmp,先前alloca状态也将恢复。这是因为堆栈指针寄存器本身被用于分配,并且(有必要)保存和恢复到jmp_buf

顺便说一句,如果以此方式工作,它提供了一种合理的机制来故意释放用分配的内存alloca

我已经将其作为错误的根本原因。

  • 这就是它应该做的 - `longjmp` 返回并让它忘记堆栈中发生的所有事情:所有变量、函数调用等。 而 `alloca` 就像堆栈上的一个数组,所以预计它们会像堆栈中的其他所有东西一样被破坏。 (3认同)
  • `man alloca` 给出了以下句子:“因为 alloca() 分配的空间是在堆栈帧内分配的,如果调用 longjmp(3) 或 siglongjmp(3) 跳过函数返回,则该空间会自动释放.”。因此,据记载,在执行“longjmp”之后,使用“alloca”分配的内存会被破坏。 (3认同)
  • 我不明白为什么这应该是意外的。当您先设置setjmp然后再分配alloca然后再选择longjmp时,通常会回绕alloca。“ longjmp”的全部要点是回到保存在“ setjmp”上的状态! (2认同)
  • 在我看来,这证明了 `setjmp`/`longjmp` 是危险的野兽。 (2认同)