首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Alloca实现

Alloca实现
EN

Stack Overflow用户
提问于 2009-04-03 16:28:20
回答 11查看 11.5K关注 0票数 34

如何在D、C和C++等语言中使用内联x86汇编器实现alloca()?我想创建一个稍微修改过的版本,但首先我需要知道标准版本是如何实现的。从编译器中读取反汇编没有任何帮助,因为它们执行了太多的优化,而我只想要规范的形式。

编辑:我想最难的部分是我想让它具有正常的函数调用语法,例如使用一个裸函数或其他什么,让它看起来像普通的alloca()。

编辑# 2:啊,怎么回事,你可以假设我们没有遗漏帧指针。

EN

回答 11

Stack Overflow用户

回答已采纳

发布于 2009-04-03 17:04:45

实现alloca实际上需要编译器辅助。这里的一些人说这很简单,就像:

代码语言:javascript
复制
sub esp, <size>

不幸的是,这只是图片的一半。是的,这将“在堆栈上分配空间”,但有几个问题。

如果编译器已发出引用相对于esp而不是ebp的其他变量的代码,则为

  1. (如果不使用帧指针进行编译,则通常如此)。然后需要调整这些引用。即使使用帧指针,编译器也会执行此sometimes.
  2. more操作。重要的是,根据定义,当函数退出时,必须“释放”使用alloca分配的空间。

最大的问题是第二点,因为你需要,编译器会在函数的每个出口点对称地将<size>加到esp中。

最可能的情况是编译器提供了一些内部函数,允许库编写者向编译器请求所需的帮助。

编辑:

事实上,在glibc (GNU的libc实现)中。alloca的实现很简单:

代码语言:javascript
复制
#ifdef  __GNUC__
# define __alloca(size) __builtin_alloca (size)
#endif /* GCC.  */

编辑:

仔细考虑后,我认为最低限度需要编译器在任何使用alloca的函数中始终使用帧指针,而不考虑优化设置。这将允许通过ebp安全地引用所有本地变量,并通过将帧指针恢复到esp来处理帧清理。

编辑:

所以我做了一些类似这样的实验:

代码语言:javascript
复制
#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:

代码语言:javascript
复制
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

正如您所看到的,这并不是那么简单。不幸的是,我坚持我最初的断言,你需要编译器的帮助。

票数 58
EN

Stack Overflow用户

发布于 2009-04-03 16:54:03

这将是棘手的-事实上,除非你对编译器的代码生成有足够的控制,否则它不能完全安全地完成。你的例程必须操作堆栈,这样当它返回时,所有的东西都会被清除,但堆栈指针保持在这样一个位置,即内存块仍然留在那个位置。

问题是,除非你能通知编译器堆栈指针已经在你的函数调用中被修改了,否则它很可能决定它可以继续通过堆栈指针引用其他局部变量(或其他任何东西)-但偏移量将是不正确的。

票数 7
EN

Stack Overflow用户

发布于 2009-04-04 19:50:49

C和C++标准并没有指定alloca()必须使用这个堆栈,因为alloca()不在C或C++标准(或POSIX )中。

编译器也可以使用堆来实现alloca()。例如,ARM RealView (RVCT)编译器的alloca()使用malloc()来分配缓冲区(referenced on their website here),并且还使编译器发出在函数返回时释放缓冲区的代码。这不需要使用堆栈指针,但仍然需要编译器支持。

Microsoft Visual C++有一个_malloca()函数,它在堆栈上没有足够的空间时使用堆,但它要求调用者使用_freea(),这与_alloca()不需要/不希望显式释放不同。

(使用C++析构函数,显然可以在没有编译器支持的情况下进行清理,但是不能在任意表达式中声明局部变量,所以我不认为可以编写使用RAII的alloca()宏。不过,显然您无论如何都不能在某些表达式(如function parameters)中使用alloca()。)

²是的,编写一个简单地调用system("/usr/games/nethack")alloca()是合法的。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/714692

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档