某些条件可能导致x86 Linux系统上的堆栈溢出:
struct my_big_object[HUGE_NUMBER]在堆栈上.走过它最终会导致SIGSEGV.alloca()程序(类似malloc(),但使用堆栈,将自动释放自己,也与吹起来SIGSEGV,如果它太大).更新:alloca()未按我原先的说法正式弃用; 它只是气馁.有没有办法以编程方式检测本地堆栈是否足够大于给定对象?我知道堆栈大小是可调整的ulimit,所以我希望有一种方法(但它可能是不可移植的).理想情况下,我希望能够做到这样的事情:
int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
char *foo = alloca(object_size);
do_stuff(foo);
}
else
{
char *foo = malloc(object_size);
do_stuff(foo);
free(foo);
}
Run Code Online (Sandbox Code Playgroud) 引用第2 BUGS节,从手册页alloca(3)
在许多系统
alloca()中不能在函数调用的参数列表中使用,因为保留的堆栈空间alloca()将出现在函数参数的空间中间的堆栈中.
我没看到这会怎么样.以下面的代码为例:
void f(int a, void * b, int c);
int
main(void)
{
f(1, alloca(100), 2);
}
Run Code Online (Sandbox Code Playgroud)
根据我的理解,alloca将堆栈帧扩展为main100字节(通过修改堆栈指针寄存器),然后指向堆栈存储器块(以及2 int秒)的指针在堆栈帧上传递f.所以分配的空间不应该在中间a,b或者c实际上它应该在不同的帧上(main在这种情况下,它在帧上).
那么这里的误解是什么?
class MyString
{
public:
MyString(int length):_ptr(alloca(length))
{
}
//Copy Constructor, destructor, other member functions.
private:
void* _ptr;
};
int main()
{
MyString str(44);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
它是在main函数的末尾释放还是在构造函数执行后立即释放?如果上面的代码按预期工作,那么有一个像这样的字符串类是一个好主意吗?
更新:
看起来主要的危险是
我想我可以通过使用alloca来实现小尺寸和malloc/free来处理大尺寸的StackOverflow.我想必须有一些非可移植的编译器特定方式来强制编译器内联.
我感兴趣,因为字符串类是在任何c ++项目中广泛使用的东西.如果我做对了,我期望获得巨大的性能提升,因为大多数分配都会进入堆栈,否则会进入堆.这将是一个实用程序,最终用户将不会意识到内部.
在大多数平台上,alloca只需归结为堆栈指针的内联调整(例如,从rspx64 减去,加上一些逻辑来维持堆栈对齐).
我正在查看gcc为alloca生成的代码,这很奇怪.采用以下简单示例1:
#include <alloca.h>
#include <stddef.h>
volatile void *psink;
void func(size_t x) {
psink = alloca(x);
}
Run Code Online (Sandbox Code Playgroud)
这将编译为以下程序集-O2:
func(unsigned long):
push rbp
add rdi, 30
and rdi, -16
mov rbp, rsp
sub rsp, rdi
lea rax, [rsp+15]
and rax, -16
mov QWORD PTR psink[rip], rax
leave
ret
Run Code Online (Sandbox Code Playgroud)
这里有几个令人困惑的事情.我明白gcc需要将分配的大小四舍五入到16的倍数(以保持堆栈对齐),通常的方法是这样做,(size + 15) & ~0xF但是它会增加30 add rdi, 30?那是怎么回事?
其次,我只希望结果alloca是新rsp值,它已经很好地对齐了.相反,gcc这样做:
lea rax, [rsp+15]
and …Run Code Online (Sandbox Code Playgroud) 为什么不alloca检查它是否可以分配内存?
来自man 3 alloca:
如果分配导致堆栈溢出,则程序行为未定义....如果无法扩展堆栈帧,则没有错误指示.
为什么alloca不能/不能检查它是否可以分配更多内存?
我理解的方式是alloca在堆栈上分配内存时在堆栈上(s)brk分配内存.来自https://en.wikipedia.org/wiki/Data_segment#Heap:
堆区域由malloc,calloc,realloc和free管理,可以使用brk和sbrk系统调用来调整其大小
来自man 3 alloca:
alloca()函数在调用者的堆栈帧中分配空间的大小字节.
并且堆栈和堆在收敛方向上增长,如此Wikipedia图中所示:
(上图来自Dougimed的Wikimedia Commons,发布于CC BY-SA 3.0)
现在,这两个alloca并(s)brk返回一个指向新分配内存的开始,这意味着它们必须知道哪里栈/堆结束当前时刻.的确,来自man 2 sbrk:
以增量0调用sbrk()可用于查找程序中断的当前位置.
因此,他们理解它,检查是否alloca可以分配所需的内存,实质上归结为检查堆栈的当前末端和堆的当前末尾之间是否有足够的空间.如果在堆栈上分配所需的内存会使堆栈到达堆,则分配失败; 否则,它会成功.
那么,为什么不能使用这样的代码来检查是否alloca可以分配内存?
void *safe_alloca(size_t size)
{
if(alloca(0) - sbrk(0) < size) {
errno = ENOMEM;
return (void *)-1;
} else {
return alloca(size);
}
}
Run Code Online (Sandbox Code Playgroud)
这对我来说更加困惑,因为显然(s)brk可以做这样的检查.来自man 2 …
我正在尝试编写一个传递给函数以用作分配参数的函数;它应该接受类型为的任何有效分配器void *(*)(size_t)。但是,在尝试alloca用作分配器时,我遇到奇怪的行为-构造指向该alloca函数的函数指针可以正常编译,但会导致链接器错误:
#include <stdlib.h>
#include <alloca.h>
int main() {
void *(*foo)(size_t) = alloca;
}
Run Code Online (Sandbox Code Playgroud)
结果是
/tmp/cc8F67yC.o: In function `main':
test15.c:(.text+0x8): undefined reference to `alloca'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)
这与内联的alloca有关吗?但是当函数不需要地址时,不会仅作为优化来进行内联。实际上,使用GCC,我什至可以编写自己的版本,该版本可以在上述代码中正常运行:
static inline void *alloca(size_t n) {
return __builtin_alloca(n);
}
Run Code Online (Sandbox Code Playgroud)
标准版本的行为方式不相同是有原因的吗?
我正在尝试实现我自己的数学库,并且我从向量开始。这个想法是给类一个指向数字数组的指针,然后复制该数组并将其存储在私有变量指针给出的数据地址中。首先,我曾经alloca尝试为私有变量释放一些内存
虚拟机
namespace vml {
// Vectors
template <typename in_type, const int in_length>
class vec {
public:
vec(in_type* in_data) {
std::cout << data << std::endl;
std::copy(in_data, in_data + in_length, data);
}
vec() {
data = nullptr;
}
in_type& operator()(int index) const {
_ASSERT(0 <= index && index < in_length);
return data[index];
}
private:
in_type* data = alloca(in_length * sizeof(in_type));
};
Run Code Online (Sandbox Code Playgroud)
主程序
int main() {
int list[] = { 1,2,3 };
int list2[] = {2,4,6 };
vml::vec<int, 3> a(list); …Run Code Online (Sandbox Code Playgroud) 可能重复:
在哪些情况下alloca()有用吗?
我最近碰巧看到了alloca()函数的使用.谷歌搜索告诉我,它用于在堆栈上分配空间.我无法抓住申请表吗?此外,使用它有任何陷阱吗?
考虑以下玩具示例,该示例通过以下alloca()函数在堆栈上分配内存:
#include <alloca.h>
void foo() {
volatile int *p = alloca(4);
*p = 7;
}
Run Code Online (Sandbox Code Playgroud)
使用gcc 8.2编译上面的函数-O3,得到以下汇编代码:
foo:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
leaq 15(%rsp), %rax
andq $-16, %rax
movl $7, (%rax)
leave
ret
Run Code Online (Sandbox Code Playgroud)
老实说,我本来期望更紧凑的汇编代码.
andq $-16, %rax上面代码中的指令导致在地址和(包括两者)之间rax包含(仅)16字节对齐的地址.rsprsp + 15
这种对齐强制执行是我不明白的第一件事:为什么alloca()将分配的内存与16字节边界对齐?
无论如何,让我们考虑我们希望分配的内存alloca()是16字节对齐的.即便如此,在上面的汇编代码中,请记住GCC假设堆栈在执行函数调用时(即call foo)执行16字节边界,如果我们注意内部堆栈的状态foo() 在推送rbp寄存器之后:
Size Stack RSP mod 16 Description
-----------------------------------------------------------------------------------
------------------
| . …Run Code Online (Sandbox Code Playgroud) #include <iostream>
#include <malloc.h>
void print_vals(int n)
{
int *arr = (int *)alloca(n);
for (int i = 0; i < n; i++)
arr[i] = i;
for (int i = 0; i < n; i++)
std::cout << arr[i] << ' ';
std::cout << '\n';
}
int main()
{
print_vals(5);
print_vals(10);
}
Run Code Online (Sandbox Code Playgroud)
当我运行此代码时,每次调用都会收到此错误:
Run-Time Check Failure #4 - Stack area around _alloca memory reserved by this function is corrupted
Run Code Online (Sandbox Code Playgroud)
我使用的是 Visual C++ 2019,stdc++14 和 stdc++17 都会产生相同的错误。
这段代码有什么问题?
alloca ×10
c ×6
c++ ×3
allocation ×2
gcc ×2
stack ×2
x86 ×2
arrays ×1
assembly ×1
linux ×1
optimization ×1
sbrk ×1
stack-memory ×1
x86-64 ×1