编辑:正如迈克尔在评论中指出的那样,由于优化编译器的堆栈相对寻址,下面写的内容在现实世界中确实不起作用。因此,生产级alloca
需要编译器的帮助才能真正“工作”。但希望下面的代码可以提供一些关于幕后发生的事情的想法,以及alloca
如果没有堆栈相关的寻址优化需要担心的话,类似的函数可能会如何工作。
顺便说一句,以防万一您仍然对如何为自己制作一个简单的版本感到好奇alloca
,因为该函数基本上返回一个指向堆栈上分配空间的指针,您可以在汇编中编写一个可以正确操作堆栈的函数,并且返回一个指针,您可以在调用者的当前范围内使用(一旦调用者返回,此版本的堆栈空间指针alloca
将失效,因为调用者的返回会清理堆栈)。
假设您在使用 Unix 64 位 ABI 的 x86_64 平台上使用某种 Linux 风格,请将以下内容放在名为“my_alloca.s”的文件中:
.section .text
.global my_alloca
my_alloca:
movq (%rsp), %r11 # save the return address in temp register
subq %rdi, %rsp # allocate space on stack from first argument
movq $0x10, %rax
negq %rax
andq %rax, %rsp # align the stack to 16-byte boundary
movq %rsp, %rax # save address in return register
pushq %r11 # push return address on stack
ret # return back to caller
然后在您的 C/C++ 代码模块(即您的“.cpp”文件)中,您可以通过以下方式使用它:
extern my_alloca(unsigned int size);
void function()
{
void* stack_allocation = my_alloca(BUFFERSIZE);
//...do something with the allocated space
return; //WARNING: stack_allocation will be invalid after return
}
您可以使用gcc -c my_alloca.s
. 这将为您提供一个名为“my_alloca.o”的文件,然后您可以使用该文件与您的其他目标文件(使用gcc -o
或 using链接ld
)。
我能想到的这个实现的主要“陷阱”是,如果编译器无法通过使用激活记录和堆栈基指针在堆栈上分配空间(即RBP
x86_64 中的指针),而是为每个函数调用显式分配内存。然后,由于编译器不会知道我们在堆栈上分配的内存,当它在调用者返回时清理堆栈并尝试使用它认为是被推送的调用者的返回地址跳转回来在函数调用开始时的堆栈上,它将跳转到指向 no-wheres-ville 的指令指针,并且您很可能会因总线错误或某种类型的访问错误而崩溃,因为您将尝试在不允许的内存位置执行代码。
实际上可能会发生其他危险的事情,例如编译器是否使用堆栈空间来分配参数(根据 Unix 64 位 ABI,它不应该用于此函数,因为只有一个参数),因为这会再次导致在函数调用之后立即进行堆栈清理,从而弄乱了指针的有效性。但是对于像这样的函数execvp()
,除非出现错误,否则它不会返回,这应该不是什么大问题。
总而言之,这样的功能将依赖于平台。