一个人如何实现alloca()使用内联x86汇编中的语言,如D、C、C++?我想创建一个略微的修改版本,但首先我需要知道如何的标准版本执行。阅读的拆卸从汇编者没有帮助,因为他们执行这么多的优化,我只想的规范的形式。

编辑:我猜硬的部分是,我想这具有正常功能呼叫法,即使用一个裸体的功能或者什么东西,让它看起来像正常alloca().

编辑#2:啊,到底什么,你可以假设,我们不忽略框架的指针。

有帮助吗?

解决方案

实施 alloca 实际上 需要编译器帮助. 。这里有一些人说这很简单:

sub esp, <size>

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

  1. 如果编译器发出了代码,以引用其他变量相对于其他变量 esp 代替 ebp(如果没有框架指针,则典型的编译)。然后需要调整这些参考。即使使用帧指针,编译器有时也会这样做。

  2. 更重要的是,根据定义,分配的空间 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

正如您所看到的,事情并不那么简单。不幸的是,我坚持我最初的主张,即您需要编译器帮助。

其他提示

这将是棘手的做到这一点 - 事实上,除非你有在编译器的代码生成不能完全安全进行足够的控制。您的例程将不得不操纵堆栈,以使得当它返回一切都被清洗,但堆栈指针留在这样一个位置,所述存储器块留在那个地方。

问题是,除非你能通知堆栈指针已经在你的函数调用修改的编译器,它可以决定它可以继续参考其他当地人(或其他)通过堆栈指针 - 但偏移量将不正确。

C和C++的标准,不指定 alloca() 已经使用堆,因为 alloca() 是不是在C或C++的标准(或POSIX对于这个问题)1中。

一个编译器,也可以实现 alloca() 使用堆。例如,手臂Arm(RVCT)编译器 alloca() 使用 malloc() 分配的缓冲区(引用他们的网站在这里),并且导致编译器,以这些代码,可以释放缓冲功能返回。这不需要玩堆的指针,但是仍然需要编译器的支持。

微软视C++有一个 _malloca() 功能,使用堆如果没有足够的房间在堆,但它要求呼叫者使用 _freea(), 不像 _alloca(), 这并不需要/要明确的释放。

(C++析构在您的处置,则显然可以做清理没有编译器的支持,但是你不能宣布的局部变量内部的一个任意的表达,所以我不认为你能写 alloca() 宏使用RAII.再说,显然你不能使用 alloca() 在某些表现形式(喜欢 功能参数)。)

1是的,它是法律的编写 alloca() 那只是电话 system("/usr/games/nethack").

ALLOCA在汇编代码直接实现。 那是因为你不能从高级语言直接控制栈布局。

另外请注意,大多数实施将执行一些额外的优化校准一样堆叠性能方面的原因。 上X86分配堆栈空间的标准方式是这样的:

sub esp, XXX

鉴于XXX是字节数到allcoate

修改结果 如果你想看看实现(和你使用的MSVC)看到alloca16.asm和chkstk.asm。结果 在第一文件中的代码基本上对齐所需的分配大小为16字节的边界。在第2个文件中的代码实际上走这将属于新的堆栈区,触动他们的所有页面。这可能会引发其中使用由OS中生长的堆PAGE_GAURD异常。

继续传递风格ALLOCA

变长数组中的纯ISO C ++ 即可。验证的概念的实现。

用法

void foo(unsigned n)
{
    cps_alloca<Payload>(n,[](Payload *first,Payload *last)
    {
        fill(first,last,something);
    });
}

核心理念

template<typename T,unsigned N,typename F>
auto cps_alloca_static(F &&f) -> decltype(f(nullptr,nullptr))
{
    T data[N];
    return f(&data[0],&data[0]+N);
}

template<typename T,typename F>
auto cps_alloca_dynamic(unsigned n,F &&f) -> decltype(f(nullptr,nullptr))
{
    vector<T> data(n);
    return f(&data[0],&data[0]+n);
}

template<typename T,typename F>
auto cps_alloca(unsigned n,F &&f) -> decltype(f(nullptr,nullptr))
{
    switch(n)
    {
        case 1: return cps_alloca_static<T,1>(f);
        case 2: return cps_alloca_static<T,2>(f);
        case 3: return cps_alloca_static<T,3>(f);
        case 4: return cps_alloca_static<T,4>(f);
        case 0: return f(nullptr,nullptr);
        default: return cps_alloca_dynamic<T>(n,f);
    }; // mpl::for_each / array / index pack / recursive bsearch / etc variacion
}

LIVE DEMO

cps_alloca 在github

可以检查一个开源C编译器的源,如打开WATCOM 和自己发现它

如果你不能使用C99的可变长度数组,可以用一个复合文字浇铸到一个空指针。

#define ALLOCA(sz) ((void*)((char[sz]){0}))

这也适用于-ansi(作为一个gcc扩展名),即使它是一个函数参数;

some_func(&useful_return, ALLOCA(sizeof(struct useless_return)));

的缺点是,当作为C ++编译,克++> 4.6会给你一个错误:服用临时数组的地址 ...铛和ICC不抱怨虽然

我们想要做的是这样的事情:

void* alloca(size_t size) {
    <sp> -= size;
    return <sp>;
}

在大会(Visual Studio中2017年,64位),它看起来像:

;alloca.asm

_TEXT SEGMENT
    PUBLIC alloca
    alloca PROC
        sub rsp, rcx ;<sp> -= size
        mov rax, rsp ;return <sp>;
        ret
    alloca ENDP
_TEXT ENDS

END

不幸的是退货指针在栈上的最后一个项目,我们不希望将其覆盖。此外,我们需要照顾的定位,即圆形的尺寸的高达8多所以我们必须做到这一点:

;alloca.asm

_TEXT SEGMENT
    PUBLIC alloca
    alloca PROC
        ;round up to multiple of 8
        mov rax, rcx
        mov rbx, 8
        xor rdx, rdx
        div rbx
        sub rbx, rdx
        mov rax, rbx
        mov rbx, 8
        xor rdx, rdx
        div rbx
        add rcx, rdx

        ;increase stack pointer
        pop rbx
        sub rsp, rcx
        mov rax, rsp
        push rbx
        ret
    alloca ENDP
_TEXT ENDS

END

ALLOCA是容易,你只移动堆栈指针向上;然后产生所有的读/写指向这个新的块

sub esp, 4

我推荐的“输入”指令。可在286个更新的处理器(已经可以在186为好,我不记得随便,但那些尚未得到广泛应用反正)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top