alloca() 分配存在的叠,而不是在堆,作为在的情况 malloc().所以,当我回到从日常的记忆被释放。所以,实际上这可以解决我的问题释放的动态分配的存储器。释放的分配通过存储器 malloc() 是一个大难题,如果以某种方式错过了导致各种各样的记忆问题。

为什么使用 alloca() 气馁,尽管有上述特点?

有帮助吗?

解决方案

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

  

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

这并不是说永远不应该使用它。我工作的其中一个OSS项目广泛使用它,只要你不滥用它(alloca'巨大的价值),它就没问题。一旦你越过<!>“几百字节<!>”;马克,是时候使用malloc和朋友了。你可能仍然会遇到分配失败,但至少你会有一些失败的迹象,而不是只是吹掉堆栈。

其他提示

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

在头文件中:

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

在实施文件中:

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

所以发生的事情是编译器内联DoSomething函数,并且所有堆栈分配都发生在Process()函数内部,从而炸毁了堆栈。在我的辩护中(我不是那个发现问题的人;当我无法解决问题时,我不得不去找其中一位高级开发人员),这不是直接的<=>,它是其中之一ATL字符串转换宏。

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

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

char arr[size];

而不是

char *arr=alloca(size);

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

alloca()是非常有用的,如果你不能用一个标准的地方变量,因为它的大小会需要确定在运行时,你可以 绝对保证,将指针,你从alloca()将永远不会被使用之后,这一功能将返回.

你可以相当安全,如果你

  • 不返回的指针,或任何含有它。
  • 不存储的指针在任何结构上分配堆
  • 不要让任何其他线使用的指针

真正危险来自的机会,其他人将会违反这些条件晚些时候。铭记这一点,这是伟大的,用于通过缓冲功能,格式的文本纳入它们:)

正如在 这个新闻发布, 有几个原因为什么使用 alloca 可以认为困难和危险的:

  • 不是所有的支助编译器 alloca.
  • 一些编纂者解释目的的行为 alloca 不同的,所以可移植性是不能保证即使是间编译器,支持它。
  • 一些实现的越野车。

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

仍然alloca使用气馁,为什么?

我不认为这样的共识。许多强大的利弊;几个缺点:

  • C99提供了变量的长度阵列,这将常常被用作为优先的符号更符合长度固定阵列,直观的整体
  • 许多系统具有较少的总体记忆/地址的空间可用于堆叠比他们对堆,这使得程序稍微更容易受到存耗竭(通过堆溢):这可以看作是一个很好的或坏的事情-原因之一叠不会自动增长的方式堆不是为了防止失控的程序从具有同样多的不利影响整个机器
  • 当用于更多的当地范围(例如一个 whilefor 环)或在几个范围,存储器的积累,每次迭代/范围并不是获释,直到的功能退出:这与正常的变量定义范围内的一个控制结构(例如 for {int i = 0; i < 2; ++i) { X } 会累积 alloca-ed存储要求在X,但是存储器,用于一个固定型阵列会再循环每次迭代)。
  • 现代的编译器通常不 inline 功能的电话 alloca, 但如果你强迫他们的 alloca 会发生在呼叫者的背景下(即该堆不会被释放,直到呼叫者的返回)
  • 很久以前 alloca 转变,从一个非便携式的功能/技巧标准化的扩展,但是一些负面的看法可能持续
  • 寿命是必要的职能范围,这可能会或可能不适合程序员比 malloc's明确的控制
  • 具有使用 malloc 鼓励思考解除-如果是通过管理的包装的功能(例如 WonderfulObject_DestructorFree(ptr)),那么该功能提供一点对于执行清理作业(如关闭文件描述,释放内部指针或做的一些记录)没有明确的变化向客户代码:有时候,这是一个好的模式采用一贯的
    • 在这个伪OO式的编程,这是自然的到想要的东西喜欢 WonderfulObject* p = WonderfulObject_AllocConstructor(); -那是可能的,当"构造"是一个功能返回 malloc-ed存储器(作为记忆仍然是分配之后返回功能价值是存在 p的),但是没有如果"构造"使用 alloca
      • 宏版本的 WonderfulObject_AllocConstructor 可以实现这一点,但是"宏邪恶",因为它们可以彼此冲突并非宏代码和创建无意替代以及随之而来的困难诊断的问题
    • 失踪 free 操作可以通过检测才,净化等。但缺少的"析构"的呼吁不能总是被检测到有一个非常脆弱的利益条款的执行预期使用情况;一些 alloca() 实现(例如海湾合作委员会的)使用一个内联宏 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
    }
}

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

换句话说,alloca( 0x00ffffff )是危险的,可能会导致溢出,与char hugeArray[ 0x00ffffff ];完全一样。要小心谨慎,你会没事的。

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

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

我建议通过包装alloca并在内部跟踪来限制最大分配大小。如果你真的很蠢,你可以在你的函数顶部抛出一些范围哨事来跟踪函数范围内的任何alloca分配,并且根据项目允许的最大数量进行健全检查。

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

alloca() 是好的和有效的...但它也是深深地打破。

  • 破范围的行为(功能范围,而不是框范围)
  • 使用inconsistant与malloc(alloca()-泰德指针不应该被释放,从今以后,你必须跟踪你的指针是来自于 免费() 只有那些你了 malloc())
  • 不良行为时,也使用内联(范围有时去叫功能取决于如果被调用内联或不)。
  • 没有堆边界检查
  • 不确定的行为在故障的情况下(不返回NULL像malloc...什么故障装置,因为它没有复叠的界限无论如何...)
  • 不ansi标准

在大多数情况下,你可以替代它使用当地的变量和majorant大小。如果这是用于大型的对象,把它们堆上通常是一个更加安全的想法。

如果你真的需要它C可以使用无线(不是用C++,太糟糕).他们是更好的比alloca()关于范围的行为,和一致性。因为我看到它 是一种 alloca() 由的权利。

当然当地结构或阵列,使用一个majorant的需要的空间仍然是更好的,如果你没有这种majorant堆分配使用普通malloc()可能是明智的。我看没有理智的使用情况的你真的真的需要 alloca()是.

很多有趣的答案<!>“旧<!>”;问题,甚至一些相对较新的答案,但我没有发现任何提及这个....

  

如果使用得当且小心,一致地使用alloca()   (可能是应用程序范围内的)处理小的可变长度分配   (或C99 VLA,如果可用)可导致降低整体堆栈   增长比使用超大的其他等效实现   固定长度的局部数组。因此,如果您仔细使用,<=>可能 适合您的筹码

我发现引用....好吧,我把这个引用了。但是真的,想一想......

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

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

<=>(或VLA)可能只是该工作的正确工具。

我见过时间<!> amp;程序员再次使用堆栈分配的缓冲区<!>“大到足以处理任何可能的情况<!>”;在深度嵌套的调用树中,重复使用该(反 - ?)模式会导致堆栈使用过度。 (想象一下20级深度的调用树,其中每个级别出于不同的原因,该函数盲目地过度分配1024字节的缓冲区<!>;只是为了安全<!>;当通常它只会使用16或少数情况下,只有在极少数情况下可能会使用更多。)另一种方法是使用<=>或VLA并仅分配与函数需要相同的堆栈空间,以避免不必要地增加堆栈负担。希望当调用树中的一个函数需要大于正常的分配时,调用树中的其他函数仍然使用它们的正常小分配,并且整个应用程序堆栈的使用量明显少于每个函数盲目地过度分配本地缓冲区的情况。

但是如果你选择使用<=> ...

基于此页面上的其他答案,似乎VLA应该是安全的(如果在循环内调用它们不会复合堆栈分配),但如果您使用<=>,请注意不要使用它在循环内部,并且确定如果有可能在另一个函数的循环中调用它,则无法内联函数。

原因如下:

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

并不是所有人都会编写这段代码,但是你传递给alloca的大小参数几乎肯定来自某种输入,这可能会恶意地使你的程序<=>这样的大事。毕竟,如果大小不是基于输入或者没有可能变大,为什么不直接声明一个小的,固定大小的本地缓冲区呢?

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

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

如果你不小心写了超出用alloca分配的块(例如由于缓冲区溢出),那么你将覆盖你的函数的返回地址,因为那个位于<! > QUOT;上述<> QUOT!;在堆栈上,即在分配的块后

这样做的后果是双重的:

  1. 程序会崩溃,并且无法判断崩溃的原因或位置(由于覆盖的帧指针,堆栈最有可能放松到随机地址)。

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

  3. 相反,如果你在堆上的块之外写,你只需<!>;只需<!>得到堆腐败。该程序可能会意外终止,但会正确展开堆栈,从而减少恶意代码执行的可能性。

alloca的一个缺陷是longjmp将其倒回。

也就是说,如果用setjmp保存上下文,然后jmp_buf保存一些内存,然后<=>保存到上下文,则可能会丢失<=>内存(没有任何通知)。堆栈指针返回原处,因此不再保留内存;如果你调用一个函数或做另一个<=>,你将破坏原来的<=>。

为了澄清,我在这里特指的是<=>不会退回<=>发生的功能的情况!相反,函数使用<=>保存上下文;然后用<=>分配内存,最后在该上下文中发生longjmp。该函数的<=>内存并非全部释放;它只是自<=>以来分配的所有内存。当然,我说的是观察到的行为;没有记录任何我知道的<=>的要求。

文档中的重点通常是<=>内存与功能激活相关联的概念,而不是任何块;多次调用<=>只是获取更多堆栈内存,该函数在函数终止时释放。不是这样;内存实际上与过程上下文相关联。使用<=>恢复上下文时,先前的<=>状态也是如此。它是堆栈指针寄存器本身用于分配的结果,也是(必然)在<=>中保存和恢复的。

顺便说一下,如果它以这种方式工作,它提供了一种合理的机制来故意释放用<=>分配的内存。

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

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

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

鉴于其使用的罕见性及其作为标准功能的阴暗状态,编译器设计人员很可能禁用可能导致alloca出现问题的任何优化,如果需要的话使用alloca可以让它更有效。

<强>更新 由于可变长度的本地数组已被添加到C和C ++中,并且由于这些数组向编译器提供与alloca非常相似的代码生成问题,因此我看到“使用稀有和阴暗状态”不适用于底层机制;但我仍然怀疑使用alloca或VLA往往会破坏使用它们的函数中的代码生成。我欢迎来自编译器设计者的任何反馈。

可悲的是,真正令人敬畏的alloca()在几乎令人敬畏的tcc中缺失了。 Gcc确实有malloc()

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

  2. realloc()类似,它会在失败时返回一个无效指针,这会在使用MMU的现代系统上发生段错误(并希望重启那些没有)。

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

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

    如果你推得太深,你就可以确定是否存在段错(如果你有MMU)。

    请注意,当系统内存不足时,<=>不再提供,因为它返回NULL(如果已分配,也会发出段错误)。即所有你能做的就是保释,或者只是试着以任何方式分配它。

    要使用<=>我使用全局变量并将它们指定为NULL。如果指针不是NULL,我在使用<=>之前释放它。

    如果要复制任何现有数据,也可以使用<=>作为一般情况。如果要在<=>之后复制或连接,则需要先检查指针。

    3.2.5.2 alloca的优点

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

通过使用alloca(),您可以大大增加获得Stack Overflow错误的机会(如果您很幸运,或者如果您不幸,则会出现无法解释的崩溃)。

不是很漂亮,但如果性能真的很重要,你可以在堆栈上预先分配一些空间。

如果你现在已经是你需要的内存块的最大大小,并且你想要保持溢出检查,你可以做类似的事情:

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}

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

alloca功能很棒,并且所有反对者都在简单地传播FUD。

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

数组和parray完全相同,风险相同。说一个比另一个更好是一种语法选择,而不是技术选择。

至于选择堆栈变量与堆变量,对于具有范围内生命周期的变量,使用堆栈堆栈的长期运行程序有很多优点。您可以避免堆碎片,并且可以避免使用未使用的(不可用的)堆空间来增加进程空间。你不需要清理它。您可以控制进程的堆栈分配。

为什么这么糟糕?

恕我直言,alloca被认为是不良做法,因为每个人都害怕耗尽堆尺寸限制。

我学到了很多通过阅读这线及其他一些链接:

我用alloca主要是为了使我的纯C的文件可编译上msvc和海湾合作委员会没有任何改变,C89的风格,没有#ifdef_MSC_VER,等等。

谢谢你!这个线让我签了这个网站:)

在我看来,alloca()(如果可用)应该只能以受约束的方式使用。非常类似于使用<!>“goto <!>”,相当多的其他合理的人不仅对alloca()的使用有强烈的厌恶,而且还存在于alloca()。

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

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

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

在嵌入式空间中,可移植性不是一个问题,但是在精心控制的情况之外,它是反对使用alloca()的一个很好的论据。

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

这里的大多数答案都很难忽略这一点:使用_alloca()可能比仅仅在堆栈中存储大型对象更糟糕。

自动存储和<=>之间的主要区别在于后者遇到了一个额外的(严重)问题:分配的块不受编译器控制,因此编译器无法使用优化或回收它。

比较

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

使用:

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top