实现alloca 实际上需要编译器帮助。这里有一些人说这很简单:
sub esp, <size>
不幸的是,这只是图片的一半。是的,这会“在堆栈上分配空间”,但有几个问题。
如果编译器已发出代码
引用其他变量
相对于esp 而不是ebp
(典型的,如果你编译没有
帧指针)。那么那些
参考文献需要调整。即使使用帧指针,编译器有时也会这样做。
更重要的是,根据定义,alloca 分配的空间必须是
函数退出时“释放”。
最重要的是第 2 点。因为您需要编译器发出代码以在函数的每个退出点对称地将<size> 添加到esp。
最可能的情况是编译器提供了一些内在函数,允许库编写者向编译器寻求所需的帮助。
编辑:
实际上,在 glibc(GNU 的 libc 实现)中。 alloca 的实现就是这样:
#ifdef __GNUC__
# define __alloca(size) __builtin_alloca (size)
#endif /* GCC. */
编辑:
经过考虑,我认为编译器至少需要始终在任何使用alloca 的函数中使用帧指针,而不管优化设置如何。这将允许通过ebp 安全地引用所有本地变量,并且将通过将帧指针恢复到esp 来处理帧清理。
编辑:
所以我做了一些这样的实验:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define __alloca(p, N) \
do { \
__asm__ __volatile__( \
"sub %1, %%esp \n" \
"mov %%esp, %0 \n" \
: "=m"(p) \
: "i"(N) \
: "esp"); \
} while(0)
int func() {
char *p;
__alloca(p, 100);
memset(p, 0, 100);
strcpy(p, "hello world\n");
printf("%s\n", p);
}
int main() {
func();
}
不幸的是无法正常工作。通过 gcc 分析汇编输出后。似乎优化阻碍了。问题似乎在于,由于编译器的优化器完全不知道我的内联程序集,它习惯于以意想不到的顺序执行这些操作,并且仍然通过esp 引用事物。
这是生成的 ASM:
8048454: push ebp
8048455: mov ebp,esp
8048457: sub esp,0x28
804845a: sub esp,0x64 ; <- this and the line below are our "alloc"
804845d: mov DWORD PTR [ebp-0x4],esp
8048460: mov eax,DWORD PTR [ebp-0x4]
8048463: mov DWORD PTR [esp+0x8],0x64 ; <- whoops! compiler still referencing via esp
804846b: mov DWORD PTR [esp+0x4],0x0 ; <- whoops! compiler still referencing via esp
8048473: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048476: call 8048338 <memset@plt>
804847b: mov eax,DWORD PTR [ebp-0x4]
804847e: mov DWORD PTR [esp+0x8],0xd ; <- whoops! compiler still referencing via esp
8048486: mov DWORD PTR [esp+0x4],0x80485a8 ; <- whoops! compiler still referencing via esp
804848e: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048491: call 8048358 <memcpy@plt>
8048496: mov eax,DWORD PTR [ebp-0x4]
8048499: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
804849c: call 8048368 <puts@plt>
80484a1: leave
80484a2: ret
如您所见,事情并非如此简单。不幸的是,我坚持我最初的主张,即您需要编译器帮助。