为什么使用 alloca() 不被认为是好的做法?

发布于 2024-07-25 04:49:55 字数 241 浏览 14 评论 0原文

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

尽管具有上述功能,为什么不鼓励使用alloca()

alloca() allocates memory on the stack rather than on the heap, as in the case of malloc(). So, when I return from the routine the memory is freed. So, actually this solves my problem of freeing up dynamically allocated memory. Freeing of memory allocated through malloc() is a major headache and if somehow missed leads to all sorts of memory problems.

Why is the use of alloca() discouraged in spite of the above features?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(24

云朵有点甜 2024-08-01 04:49:55

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

返回值
alloca() 函数返回一个指向该对象开头的指针
分配的空间。 如果
分配原因
堆栈溢出,程序行为未定义。

这并不是说它永远不应该被使用。 我从事的 OSS 项目之一广泛使用了它,只要你不滥用它(分配'ing巨大的值),就没有问题。 一旦超过了“几百字节”标记,就该使用 malloc 和朋友了。 您可能仍然会遇到分配失败,但至少您会得到一些失败的指示,而不是仅仅破坏堆栈。

The answer is right there in the man page (at least on 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.

Which isn't to say it should never be used. One of the OSS projects I work on uses it extensively, and as long as you're not abusing it (alloca'ing huge values), it's fine. Once you go past the "few hundred bytes" mark, it's time to use malloc and friends, instead. You may still get allocation failures, but at least you'll have some indication of the failure instead of just blowing out the stack.

萌吟 2024-08-01 04:49:55

我遇到的最难忘的错误之一是与使用 alloca 的内联函数有关。 它表现为在程序执行的随机点发生堆栈溢出(因为它在堆栈上分配)。

在头文件中:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

在实现文件中:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

那么发生的事情是编译器 (Microsoft VC++ 6) 内联 DoSomething 函数,并且所有堆栈分配都发生在 Process() 函数内从而炸毁堆栈。 在我的辩护中(我不是发现这个问题的人;当我无法修复它时,我不得不去向一位高级开发人员哭泣),这不是直接的 alloca ,它是ATL字符串转换宏之一。

所以教训是 - 不要在您认为可能内联的函数中使用 alloca

One of the most memorable bugs I had was to do with an inline function that used alloca. It manifested itself as a stack overflow (because it allocates on the stack) at random points of the program's execution.

In the header file:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

In the implementation file:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

So what happened was the compiler (Microsoft VC++ 6) inlined DoSomething function and all the stack allocations were happening inside Process() function and thus blowing the stack up. In my defence (and I wasn't the one who found the issue; I had to go and cry to one of the senior developers when I couldn't fix it), it wasn't straight alloca, it was one of ATL string conversion macros.

So the lesson is - do not use alloca in functions that you think might be inlined.

末が日狂欢 2024-08-01 04:49:55

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

char arr[size];

而不是

char *arr=alloca(size);

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

Old question but nobody mentioned that it should be replaced by variable length arrays.

char arr[size];

instead of

char *arr=alloca(size);

It's in the standard C99 and existed as compiler extension in many compilers.

魔法唧唧 2024-08-01 04:49:55

如果您无法使用标准局部变量,alloca() 非常有用,因为它的大小需要在运行时确定,您可以
绝对保证从alloca()获得的指针在该函数返回后永远不会被使用

则可以相当安全。

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

真正的危险来自其他人稍后可能会违反这些条件的机会。 考虑到这一点,将缓冲区传递给将文本格式化为缓冲区的函数非常有用:)

alloca() is very useful if you can't use a standard local variable because its size would need to be determined at runtime and you can
absolutely guarantee that the pointer you get from alloca() will NEVER be used after this function returns.

You can be fairly safe if you

  • do not return the pointer, or anything that contains it.
  • do not store the pointer in any structure allocated on the heap
  • do not let any other thread use the pointer

The real danger comes from the chance that someone else will violate these conditions sometime later. With that in mind it's great for passing buffers to functions that format text into them :)

不知所踪 2024-08-01 04:49:55

正如此新闻组帖子中所述,使用 < code>alloca 可以被认为是困难和危险的:

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

As noted in this newsgroup posting, there are a few reasons why using alloca can be considered difficult and dangerous:

  • Not all compilers support alloca.
  • Some compilers interpret the intended behaviour of alloca differently, so portability is not guaranteed even between compilers that support it.
  • Some implementations are buggy.
丑疤怪 2024-08-01 04:49:55

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

One issue is that it isn't standard, although it's widely supported. Other things being equal, I'd always use a standard function rather than a common compiler extension.

对风讲故事 2024-08-01 04:49:55

仍然不鼓励使用alloca,为什么?

我不认为有这样的共识。 许多强大的职业选手; 一些缺点:

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

    • 缺失的free操作可以被ValGrind、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
    }
}

still alloca use is discouraged, why?

I don't perceive such a consensus. Lots of strong pros; a few cons:

  • 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:

    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.


I know this question is tagged C, but as a C++ programmer I thought I'd use C++ to illustrate the potential utility of alloca: the code below (and here at ideone) creates a vector tracking differently sized polymorphic types that are stack allocated (with lifetime tied to function return) rather than heap allocated.

#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
    }
}
痞味浪人 2024-08-01 04:49:55

对于这个“老”问题有很多有趣的答案,甚至还有一些相对较新的答案,但我没有找到任何提到这一点的答案......

正确且小心使用时,一致使用alloca()
(也许是应用程序范围内)处理小型可变长度分配
(或 C99 VLA,如果可用)可能会导致整体堆栈降低
增长比使用超大的其他等效实现
固定长度的局部数组。 因此,如果您谨慎使用,alloca() 可能对您的堆栈有好处

我发现这句话是在……好吧,那句话是我编的。 但实际上,想一想......

@j_random_hacker 在其他答案下的评论是非常正确的:避免使用 alloca() 有利于超大的本地数组并不会让你的程序从堆栈中更安全溢出(除非您的编译器足够旧,允许内联使用 alloca() 的函数,在这种情况下您应该升级,或者除非您在循环内使用 alloca(),在这种情况下你不应该在循环内使用alloca())。

我曾从事桌面/服务器环境和嵌入式系统的工作。 许多嵌入式系统根本不使用堆(它们甚至不链接以支持它),原因包括认为动态分配内存是邪恶的,因为应用程序上永远不会存在内存泄漏的风险。一次重新启动多年,或者更合理的理由是动态内存是危险的,因为无法确定应用程序永远不会将其堆碎片化到错误的内存耗尽的程度。 因此嵌入式程序员几乎没有什么选择。

alloca()(或 VLA)可能正是完成这项工作的正确工具。

我见过时间& 再次,程序员使堆栈分配的缓冲区“足够大以处理任何可能的情况”。 在深度嵌套的调用树中,重复使用该(反?)模式会导致堆栈使用过度。 (想象一下一个 20 层深的调用树,在每一层,由于不同的原因,该函数盲目地过度分配 1024 字节的缓冲区“只是为了安全”,而通常它只会使用其中的 16 个或更少,并且仅在非常情况下极少数情况下可能会使用更多。)另一种方法是使用 alloca() 或 VLA,并仅分配函数需要的堆栈空间,以避免不必要的堆栈负担。 希望当调用树中的一个函数需要比正常分配更大的分配时,调用树中的其他函数仍在使用其正常的小分配,并且总体应用程序堆栈使用量明显低于每个函数盲目过度分配本地缓冲区的情况。

但是,如果您选择使用 alloca()...

根据本页上的其他答案,VLA 似乎应该是安全的(如果从循环内调用,它们不会复合堆栈分配),但如果您使用 alloca(),请注意不要在循环内使用它,并确保您的函数无法内联(如果有可能)在另一个函数的循环中调用。

Lots of interesting answers to this "old" question, even some relatively new answers, but I didn't find any that mention this....

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.

I found that quote in.... OK, I made that quote up. But really, think about it....

@j_random_hacker is very right in his comments under other answers: Avoiding the use of alloca() in favor of oversized local arrays does not make your program safer from stack overflows (unless your compiler is old enough to allow inlining of functions that use alloca() in which case you should upgrade, or unless you use alloca() inside loops, in which case you should... not use alloca() inside loops).

I've worked on desktop/server environments and embedded systems. A lot of embedded systems don't use a heap at all (they don't even link in support for it), for reasons that include the perception that dynamically allocated memory is evil due to the risks of memory leaks on an application that never ever reboots for years at a time, or the more reasonable justification that dynamic memory is dangerous because it can't be known for certain that an application will never fragment its heap to the point of false memory exhaustion. So embedded programmers are left with few alternatives.

alloca() (or VLAs) may be just the right tool for the job.

I've seen time & time again where a programmer makes a stack-allocated buffer "big enough to handle any possible case". In a deeply nested call tree, repeated use of that (anti-?)pattern leads to exaggerated stack use. (Imagine a call tree 20 levels deep, where at each level for different reasons, the function blindly over-allocates a buffer of 1024 bytes "just to be safe" when generally it will only use 16 or less of them, and only in very rare cases may use more.) An alternative is to use alloca() or VLAs and allocate only as much stack space as your function needs, to avoid unnecessarily burdening the stack. Hopefully when one function in the call tree needs a larger-than-normal allocation, others in the call tree are still using their normal small allocations, and the overall application stack usage is significantly less than if every function blindly over-allocated a local buffer.

But if you choose to use alloca()...

Based on other answers on this page, it seems that VLAs should be safe (they don't compound stack allocations if called from within a loop), but if you're using alloca(), be careful not to use it inside a loop, and make sure your function can't be inlined if there's any chance it might be called within another function's loop.

蓝咒 2024-08-01 04:49:55

所有其他答案都是正确的。 但是,如果您想要使用 alloca() 分配的东西相当小,我认为这是一个很好的技术,比使用 malloc() 或其他方式更快、更方便。

换句话说,alloca( 0x00ffffff ) 是危险的,并且可能导致溢出,就像 char HugeArray[ 0x00ffffff ]; 一样。 保持谨慎和理性,你会没事的。

All of the other answers are correct. However, if the thing you want to alloc using alloca() is reasonably small, I think that it's a good technique that's faster and more convenient than using malloc() or otherwise.

In other words, alloca( 0x00ffffff ) is dangerous and likely to cause overflow, exactly as much as char hugeArray[ 0x00ffffff ]; is. Be cautious and reasonable and you'll be fine.

但可醉心 2024-08-01 04:49:55

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

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

鉴于其使用的稀有性及其作为标准函数的可疑地位,编译器设计者很可能禁用任何可能导致分配问题的优化,如果需要超过一个让它与 alloca 一起工作只需付出很少的努力。

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

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.

For instance, a common optimization by C compilers is to eliminate use of the frame pointer within a function, frame accesses are made relative to the stack pointer instead; so there's one more register for general use. But if alloca is called within the function, the difference between sp and fp will be unknown for part of the function, so this optimization cannot be done.

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.

UPDATE:
Since variable-length local arrays have been added to C, and since these present very similar code-generation issues to the compiler as alloca, I see that 'rarity of use and shady status' does not apply to the underlying mechanism; but I would still suspect that use of either alloca or VLA tends to compromise code generation within a function that uses them. I would welcome any feedback from compiler designers.

胡渣熟男 2024-08-01 04:49:55

每个人都已经指出了堆栈溢出潜在的未定义行为这一大问题,但我应该提到 Windows 环境有一个很好的机制来使用结构化异常 (SEH) 和保护页来捕获这种行为。 由于堆栈仅根据需要增长,因此这些保护页驻留在未分配的区域中。 如果您分配给它们(通过溢出堆栈),则会引发异常。

您可以捕获此 SEH 异常并调用 _resetstkoflw 来重置堆栈并继续您的快乐之路。 它并不理想,但它是另一种机制,至少可以在出现问题时知道出现问题。 *nix 可能有类似的东西,但我不知道。

我建议通过包装 alloca 并在内部跟踪它来限制最大分配大小。 如果您真的对此很坚定,您可以在函数顶部放置一些范围哨兵来跟踪函数范围内的任何分配分配,并根据项目允许的最大数量进行健全性检查。

此外,除了不允许内存泄漏之外,alloca 还不会导致内存碎片,这一点非常重要。 我不认为 alloca 是不好的做法,如果你明智地使用它,这基本上对所有事情都是如此。 :-)

Everyone has already pointed out the big thing which is potential undefined behavior from a stack overflow but I should mention that the Windows environment has a great mechanism to catch this using structured exceptions (SEH) and guard pages. Since the stack only grows as needed, these guard pages reside in areas that are unallocated. If you allocate into them (by overflowing the stack) an exception is thrown.

You can catch this SEH exception and call _resetstkoflw to reset the stack and continue on your merry way. Its not ideal but it's another mechanism to at least know something has gone wrong when the stuff hits the fan. *nix might have something similar that I'm not aware of.

I recommend capping your max allocation size by wrapping alloca and tracking it internally. If you were really hardcore about it you could throw some scope sentries at the top of your function to track any alloca allocations in the function scope and sanity check this against the max amount allowed for your project.

Also, in addition to not allowing for memory leaks alloca does not cause memory fragmentation which is pretty important. I don't think alloca is bad practice if you use it intelligently, which is basically true for everything. :-)

酷炫老祖宗 2024-08-01 04:49:55

alloca 的一个陷阱是 longjmp 会倒回它。

也就是说,如果你用setjmp保存一个上下文,然后alloca一些内存,然后longjmp到上下文,你可能会丢失<代码>分配内存。 堆栈指针回到原来的位置,因此不再保留内存; 如果您调用一个函数或执行另一个alloca,您将破坏原始的alloca

为了澄清,我在这里特别指的是 longjmp 没有从发生 alloca 的函数中返回的情况! 相反,函数使用 setjmp 保存上下文; 然后使用alloca分配内存,最后对该上下文进行longjmp。 该函数的 alloca 内存并未全部释放; 只是自 setjmp 以来分配的所有内存。 当然,我说的是观察到的行为; 据我所知,没有任何 alloca 记录这样的要求。

文档中的重点通常是这样的概念:alloca 内存与函数 激活相关联,而不是与任何块相关联; 多次调用 alloca 只是获取更多的堆栈内存,这些内存在函数终止时全部释放。 并非如此; 内存实际上与过程上下文相关联。 当使用 longjmp 恢复上下文时,之前的 alloca 状态也会恢复。 这是堆栈指针寄存器本身用于分配的结果,并且(必然)在 jmp_buf 中保存和恢复。

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

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

One pitfall with alloca is that longjmp rewinds it.

That is to say, if you save a context with setjmp, then alloca some memory, then longjmp to the context, you may lose the alloca memory. The stack pointer is back where it was and so the memory is no longer reserved; if you call a function or do another alloca, you will clobber the original alloca.

To clarify, what I'm specifically referring to here is a situation whereby longjmp does not return out of the function where the alloca took place! Rather, a function saves context with setjmp; then allocates memory with alloca and finally a longjmp takes place to that context. That function's alloca memory is not all freed; just all the memory that it allocated since the setjmp. Of course, I'm speaking about an observed behavior; no such requirement is documented of any alloca that I know.

The focus in the documentation is usually on the concept that alloca memory is associated with a function activation, not with any block; that multiple invocations of alloca just grab more stack memory which is all released when the function terminates. Not so; the memory is actually associated with the procedure context. When the context is restored with longjmp, so is the prior alloca state. It's a consequence of the stack pointer register itself being used for allocation, and also (necessarily) saved and restored in the jmp_buf.

Incidentally, this, if it works that way, provides a plausible mechanism for deliberately freeing memory that was allocated with alloca.

I have run into this as the root cause of a bug.

小苏打饼 2024-08-01 04:49:55

原因如下:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

并不是任何人都会编写这段代码,但您传递给 alloca 的大小参数几乎肯定来自某种输入,这可能会恶意地让您的程序进入 alloca 像这样巨大的东西。 毕竟,如果大小不是基于输入或者不可能很大,为什么不直接声明一个小的、固定大小的本地缓冲区呢?

几乎所有使用 alloca 和/或 C99 vlas 的代码都有严重的错误,这将导致崩溃(如果你幸运的话)或特权妥协(如果你不那么幸运)。

Here's why:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

Not that anyone would write this code, but the size argument you're passing to alloca almost certainly comes from some sort of input, which could maliciously aim to get your program to alloca something huge like that. After all, if the size isn't based on input or doesn't have the possibility to be large, why didn't you just declare a small, fixed-size local buffer?

Virtually all code using alloca and/or C99 vlas has serious bugs which will lead to crashes (if you're lucky) or privilege compromise (if you're not so lucky).

哀由 2024-08-01 04:49:55

alloca() 很好而且高效……但它也被严重破坏了。

  • 损坏的作用域行为(函数作用域而不是块作用域)
  • 与 malloc (alloca()-ted 指针不应该被释放,因此您必须跟踪指针来自哪里 free() 仅限那些使用 ma​​lloc() 获得的
  • 错误行为,当您还使用内联时(作用域有时会转到调用者函数,具体取决于被调用者是否内联)。
  • 没有堆栈边界检查
  • 失败时的未定义行为(不会像 malloc 一样返回 NULL...失败意味着什么,因为它无论如何都不检查堆栈边界...)
  • 不是 ansi 标准

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

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

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

alloca () is nice and efficient... but it is also deeply broken.

  • 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

In most cases you can replace it using local variables and majorant size. If it's used for large objects, putting them on the heap is usually a safer idea.

If you really need it C you can use VLA (no vla in C++, too bad). They are much better than alloca() regarding scope behavior and consistency. As I see it VLA are a kind of alloca() made right.

Of course a local structure or array using a majorant of the needed space is still better, and if you don't have such majorant heap allocation using plain malloc() is probably sane.
I see no sane use case where you really really need either alloca() or VLA.

深爱成瘾 2024-08-01 04:49:55

alloca 并不比变长数组 (VLA) 差,但它比在堆上分配的风险更大。

在 x86 上(最常见的是 ARM 上),堆栈向下增长,这会带来一定的风险:如果您不小心写入超出使用 alloca 分配的块(由于缓冲区溢出)例如),那么您将覆盖函数的返回地址,因为该地址位于堆栈的“上方”,即在您分配的块之后

这样做的后果有两个:

  1. 程序将严重崩溃,并且无法判断崩溃的原因或位置(堆栈很可能会展开到随机地址,因为被覆盖的帧指针)。

  2. 这使得缓冲区溢出的危险性增加了许多倍,因为恶意用户可以制作一个特殊的有效负载,该有效负载将被放入堆栈中并因此最终被执行。

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

alloca is not worse than a variable-length array (VLA), but it's riskier than allocating on the heap.

On x86 (and most often on ARM), the stack grows downwards, and that brings with it a certain amount of risk: 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, i.e. after your allocated block.

_alloca block on the stack

The consequence of this is two-fold:

  1. 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).

  2. 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.

荒芜了季节 2024-08-01 04:49:55

进程只有有限的可用堆栈空间 - 远远小于 malloc() 可用的内存量。

通过使用 alloca() ,您将大大增加出现 Stack Overflow 错误的机会(如果您幸运的话,否则会出现莫名其妙的崩溃)。

Processes only have a limited amount of stack space available - far less than the amount of memory available to malloc().

By using alloca() you dramatically increase your chances of getting a Stack Overflow error (if you're lucky, or an inexplicable crash if you're not).

紫罗兰の梦幻 2024-08-01 04:49:55

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

A place where alloca() is especially dangerous than malloc() is the kernel - kernel of a typical operating system has a fixed sized stack space hard-coded into one of its header; it is not as flexible as the stack of an application. Making a call to alloca() with an unwarranted size may cause the kernel to crash.
Certain compilers warn usage of alloca() (and even VLAs for that matter) under certain options that ought to be turned on while compiling a kernel code - here, it is better to allocate memory in the heap that is not fixed by a hard-coded limit.

听不够的曲调 2024-08-01 04:49:55

遗憾的是,几乎很棒的 tcc 中缺少真正很棒的 alloca()。 Gcc 确实有 alloca()

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

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

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

它与递归配合得很好。 您可以使用静态变量来实现类似于尾递归的功能,并仅使用其他一些变量将信息传递给每次迭代。

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

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

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

如果想要复制任何现有数据,您还可以使用 realloc() 作为一般情况。 您需要先检查指针以确定是否要在 realloc() 之后进行复制或连接。

3.2. 5.2 alloca的优点

Sadly the truly awesome alloca() is missing from the almost awesome tcc. Gcc does have alloca().

  1. It sows the seed of its own destruction. With return as the destructor.

  2. Like malloc() it returns an invalid pointer on fail which will segfault on modern systems with a MMU (and hopefully restart those without).

  3. Unlike auto variables you can specify the size at run time.

It works well with recursion. You can use static variables to achieve something similar to tail recursion and use just a few others pass info to each iteration.

If you push too deep you are assured of a segfault (if you have an MMU).

Note that malloc() offers no more as it returns NULL (which will also segfault if assigned) when the system is out of memory. I.e. all you can do is bail or just try to assign it any way.

To use malloc() I use globals and assign them NULL. If the pointer is not NULL I free it before I use malloc().

You can also use realloc() as general case if want copy any existing data. You need to check pointer before to work out if you are going to copy or concatenate after the realloc().

3.2.5.2 Advantages of alloca

美人如玉 2024-08-01 04:49:55

实际上,alloca 并不能保证使用堆栈。
事实上,alloca 的 gcc-2.95 实现使用 malloc 本身从堆中分配内存。 此外,该实现存在错误,如果您在块内进一步使用 goto 调用它,则可能会导致内存泄漏和一些意外行为。 并不是说你永远不应该使用它,但有时 alloca 会导致比释放 frome 更多的开销。

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.

花开雨落又逢春i 2024-08-01 04:49:55

在我看来,alloca()(如果可用)应该仅以受约束的方式使用。 与“goto”的使用非常相似,相当多的理性人不仅对 alloca() 的使用,而且对 alloca() 的存在都抱有强烈的反感。

对于嵌入式使用,堆栈大小已知,并且可以通过分配大小的约定和分析施加限制,并且编译器无法升级以支持 C99+,使用 alloca() 就可以了,我一直在这样做已知使用它。

当可用时,VLA 可能比 alloca() 具有一些优势:编译器可以生成堆栈限制检查,当使用数组样式访问时,该检查将捕获越界访问(我不知道是否有任何编译器这样做,但它可以完成),并且分析代码可以确定数组访问表达式是否正确有界。 请注意,在某些编程环境中,例如汽车、医疗设备和航空电子设备,即使对于固定大小的数组也必须进行此分析,无论是自动(在堆栈上)还是静态分配(全局或本地)。

在堆栈上存储数据和返回地址/帧指针的体系结构上(据我所知,这就是全部),任何堆栈分配的变量都可能是危险的,因为可以获取变量的地址,并且未经检查的输入值可能允许各种恶作剧。

在嵌入式领域,可移植性不太受关注,但在严格控制的环境之外,这是反对使用 alloca() 的一个很好的论据。

在嵌入空间之外,我主要在日志记录和格式化函数中使用 alloca() 以提高效率,并且在非递归词法扫描器中使用,其中在标记化和分类期间创建临时结构(使用 alloca() 分配,然后创建持久结构)对象(通过 malloc() 分配)在函数返回之前填充,对较小的临时结构使用 alloca() 可以大大减少分配持久对象时的碎片。

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.

天荒地未老 2024-08-01 04:49:55

为什么没有人提到GNU文档中介绍的这个例子?

https://www.gnu.org/software/ libc/manual/html_node/Advantages-of-Alloca.html

使用longjmp完成非本地退出(请参阅非本地退出)自动
当它们通过 alloca 退出时,释放分配的空间
调用alloca的函数。 这是使用的最重要原因
分配

建议阅读顺序1->2->3->1

  1. https://www.gnu.org/software/libc/manual/html_node/Advantages-of-Alloca.html< /a>
  2. 简介详细信息来自非本地退出
  3. Alloca 示例

Why no one mentions this example introduced by GNU documention?

https://www.gnu.org/software/libc/manual/html_node/Advantages-of-Alloca.html

Nonlocal exits done with longjmp (see Non-Local Exits) automatically
free the space allocated with alloca when they exit through the
function that called alloca. This is the most important reason to use
alloca

Suggest reading order 1->2->3->1:

  1. https://www.gnu.org/software/libc/manual/html_node/Advantages-of-Alloca.html
  2. Intro and Details from Non-Local Exits
  3. Alloca Example
护你周全 2024-08-01 04:49:55

我认为没有人提到过这一点,但 alloca 也存在一些严重的安全问题,而 malloc 不一定存在这些问题(尽管这些问题也出现在任何基于堆栈的数组中,无论是动态的还是非动态的)。 由于内存是在堆栈上分配的,因此缓冲区溢出/下溢会比仅使用 malloc 产生更严重的后果。

特别是,函数的返回地址存储在堆栈中。 如果该值被损坏,您的代码可能会进入内存的任何可执行区域。 编译器竭尽全力让这变得困难(特别是通过随机化地址布局)。 但是,这显然比堆栈溢出更糟糕,因为如果返回值损坏,最好的情况是 SEGFAULT,但它也可能开始执行随机的内存块,或者在最坏的情况下执行某些内存区域,从而损害程序的安全性。

I don't think that anybody has mentioned this, but alloca also has some serious security issues not necessarily present with malloc (though these issues also arise with any stack based arrays, dynamic or not). Since the memory is allocated on the stack, buffer overflows/underflows have much more serious consequences than with just malloc.

In particular, the return address for a function is stored on the stack. If this value gets corrupted, your code could be made to go to any executable region of memory. Compilers go to great lengths to make this difficult (in particular by randomizing address layout). However, this is clearly worse than just a stack overflow since the best case is a SEGFAULT if the return value is corrupted, but it could also start executing a random piece of memory or in the worst case some region of memory which compromises your program's security.

何时共饮酒 2024-08-01 04:49:55

这里的大多数答案很大程度上都没有抓住要点:使用 _alloca() 可能比仅仅在堆栈中存储大对象更糟糕,这是有原因的。

自动存储和 _alloca() 之间的主要区别在于后者存在一个额外的(严重)问题:分配的块不受编译器控制,因此没有编译器优化或回收它的方式。

比较:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

与:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

后者的问题应该是显而易见的。

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.

Compare:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

with:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

The problem with the latter should be obvious.

狼性发作 2024-08-01 04:49:55

在我看来,alloca 和可变长度数组的最大风险是,如果分配大小意外地大,它可能会以非常危险的方式失败。

堆栈上的分配通常不会检查用户代码。

现代操作系统通常会在下面*放置一个保护页来检测堆栈溢出。 当堆栈溢出时,内核可能会扩展堆栈或终止进程。 Linux 在 2017 年将此保护区扩展为比页面大得多,但其大小仍然有限。

因此,一般来说,在使用之前的分配之前,最好避免在堆栈上分配多个页面。 使用分配或可变长度数组,很容易导致攻击者在堆栈上进行任意大小的分配,从而跳过任何保护页并访问任意内存。

* 在当今最广泛的系统上,堆栈向下增长。

IMO the biggest risk with alloca and variable length arrays is it can fail in a very dangerous manner if the allocation size is unexpectedly large.

Allocations on the stack typically have no checking in user code.

Modern operating systems will generally put a guard page in place below* to detect stack overflow. When the stack overflows the kernel may either expand the stack or kill the process. Linux expanded this guard region in 2017 to be significantly large than a page, but it's still finite in size.

So as a rule it's best to avoid allocating more than a page on the stack before making use of the previous allocations. With alloca or variable length arrays it's easy to end up allowing an attacker to make arbitrary size allocations on the stack and hence skip over any guard page and access arbitrary memory.

* on most widespread systems today the stack grows downwards.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文