为什么alloca()的使用不被视为良好实践?
为什么alloca()的使用不被视为良好实践?,c,stack,malloc,allocation,alloca,C,Stack,Malloc,Allocation,Alloca,alloca()在堆栈而不是堆上分配内存,如malloc()的情况。因此,当我从例程返回时,内存被释放。实际上,这解决了释放动态分配内存的问题。释放通过malloc()分配的内存是一个非常头痛的问题,如果丢失了,会导致各种内存问题 尽管有上述功能,为什么不鼓励使用alloca()?答案就在man页面上(至少在上): 返回值 函数的作用是:返回一个指向 分配的空间。如果 分配原因 堆栈溢出,程序行为未定义 这并不是说它永远不应该被使用。我工作的一个OSS项目广泛使用它,只要你没有滥用它(alloc
alloca()
在堆栈而不是堆上分配内存,如malloc()
的情况。因此,当我从例程返回时,内存被释放。实际上,这解决了释放动态分配内存的问题。释放通过malloc()
分配的内存是一个非常头痛的问题,如果丢失了,会导致各种内存问题
尽管有上述功能,为什么不鼓励使用
alloca()
?答案就在man
页面上(至少在上):
返回值
函数的作用是:返回一个指向
分配的空间。如果
分配原因
堆栈溢出,程序行为未定义
这并不是说它永远不应该被使用。我工作的一个OSS项目广泛使用它,只要你没有滥用它(
alloca
“使用巨大的值”),它就可以了。一旦超过“几百字节”标记,就应该使用malloc
和friends。您可能仍然会遇到分配失败,但至少您会有一些失败的迹象,而不是仅仅破坏堆栈。进程只有有限的可用堆栈空间-远远小于malloc()可用的内存量。
使用alloca()
会大大增加发生堆栈溢出错误的几率(如果幸运的话,或者如果不幸运的话,会发生无法解释的崩溃)。如中所述,使用alloca
会被认为是困难和危险的原因有几个:
- 并非所有编译器都支持alloca
- 有些编译器以不同的方式解释alloca的预期行为,因此即使在支持它的编译器之间也不能保证可移植性
- 有些实现有缺陷
- 所有其他答案都是正确的。但是,如果您想使用
alloca()
来分配的东西相当小,我认为这是一种比使用malloc()
或其他方法更快、更方便的好技术
换句话说,
alloca(0x00ffffff)
是危险的,很可能导致溢出,与char-hugarray[0x00ffffff]一样代码>是。谨慎、合理,你会没事的。一个问题是它不是标准的,尽管它得到了广泛的支持。在其他条件相同的情况下,我总是使用标准函数,而不是通用的编译器扩展。alloca()对于不能使用标准局部变量非常有用,因为它的大小需要在运行时确定,您可以
绝对保证从alloca()获取的指针在该函数返回后永远不会被使用。
如果你愿意,你会相当安全的
- 不要返回指针或任何包含指针的内容李>
- 不要将指针存储在堆上分配的任何结构中
- 不要让任何其他线程使用指针
真正的危险来自于其他人稍后违反这些条件的可能性。考虑到这一点,将缓冲区传递给格式化文本的函数非常好:)原因如下:
char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;
并不是说任何人都会写这段代码,但是你传递给alloca
的大小参数几乎肯定来自某种输入,它可能恶意地让你的程序运行到alloca
这样的大程序。毕竟,如果大小不是基于输入的,或者不可能很大,为什么不声明一个小的、固定大小的本地缓冲区呢
几乎所有使用alloca和/或C99 VLA的代码都有严重的错误,这些错误将导致崩溃(如果幸运的话)或权限泄露(如果幸运的话)。老问题,但没有人提到应该用可变长度数组来代替它
char arr[size];
而不是
char *arr=alloca(size);
它在标准C99中,作为编译器扩展存在于许多编译器中。我遇到的最令人难忘的错误之一是使用了alloca
的内联函数。它在程序执行的随机点表现为堆栈溢出(因为它在堆栈上分配)
在头文件中:
void DoSomething() {
wchar_t* pStr = alloca(100);
//......
}
在实现文件中:
void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
}
void进程(){
对于(i=0;i<1000000;i++){
DoSomething();
}
}
因此,所发生的是编译器内联的DoSomething
函数,所有堆栈分配都发生在Process()
函数中,从而导致堆栈崩溃。为我辩护(我不是发现问题的人;当我无法解决问题时,我不得不去向一位高级开发人员哭诉),这不是直接的alloca
,而是一个ATL字符串转换宏
所以教训是-不要在你认为可能是内联的函数中使用alloca
。一个alloca()
比malloc()
更危险的地方是内核-典型操作系统的内核有一个固定大小的堆栈空间硬编码到它的一个头中;它不如应用程序的堆栈灵活。使用不合理的大小调用alloca()
,可能会导致内核崩溃。
某些编译器警告在编译内核代码时应打开的某些选项下使用alloca()
(甚至VLA),最好在堆中分配不受硬编码限制的内存。每个人都已经指出了一个大问题,那就是堆栈溢出可能会导致未定义的行为,但我应该提到的是,Windows环境有一个很好的机制,可以使用结构化异常(SEH)和保护页来捕获这种行为。由于堆栈仅根据需要增长,因此这些保护页驻留在未分配的区域中。如果你分配给他们(通过溢出)
#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
}
}
while (condition) {
char buffer[0x100]; // Chill.
/* ... */
}
while (condition) {
char* buffer = _alloca(0x100); // Bad!
/* ... */
}