在进入功能之前,有什么方法可以保存寄存器吗?

时间:2019-03-16 11:05:46

标签: c assembly visual-c++ x86-64 calling-convention

这是我的第一个问题,因为我找不到与该主题相关的任何东西。

最近,在为我的C游戏引擎项目制作类时,我发现了一些有趣的东西:

struct Stack *S1 = new(Stack);
struct Stack *S2 = new(Stack);

S1->bPush(S1, 1, 2);               //at this point

bPush是结构中的函数指针。

所以我想知道,在这种情况下,运算符->是什么,并且我发现:

 mov         r8b,2                 ; a char, written to a low point of register r8
 mov         dl,1                  ; also a char, but to d this time
 mov         rcx,qword ptr [S1]    ; this is the 1st parameter of function
 mov         rax,qword ptr [S1]    ; !Why cannot I use this one?
 call        qword ptr [rax+1A0h]  ; pointer call

所以我假设->向 rcx 写一个对象指针,我想在函数中使用它(应该是的方法)。所以问题是,我该怎么做

 push        rcx
 // do other call vars
 pop         rcx
 mov         qword ptr [this], rcx

在开始编写函数的其他变量之前。带有预处理器的东西吗?

1 个答案:

答案 0 :(得分:2)

如果您使用C ++编写,看起来您会更轻松(并且获得相同或更高效率的asm),以便可以使用对虚拟函数以及初始化时运行构造函数的语言内置支持。更不用说不必手动运行析构函数。您不需要struct Class黑客。

  

我想隐式传递*this指针,因为正如第二个asm部分所示,它两次执行相同的操作,是的,这正是我想要的,bPush是struct的一部分,并且不能从外部调用它,但是我必须传递它已经拥有的指针S1。

由于禁用优化,您的asm效率低下。

MSVC -O2-Ox不会两次重载静态指针。确实浪费了mov指令在寄存器之间的复制,但是如果您想要更好的asm,请使用更好的编译器(例如gcc或clang)。

Godbolt编译器资源管理器上最古老的MSVC是MSVC 2015中的CL19.0,可编译此源代码

struct Stack {
    int stuff[4];
    void (*bPush)(struct Stack*, unsigned char value, unsigned char length);
};


struct Stack *const S1 = new(Stack);

int foo(){
    S1->bPush(S1, 1, 2);

    //S1->bPush(S1, 1, 2);
    return 0;  // prevent tailcall optimization
}

into this asm (Godbolt)

# MSVC 2015  -O2
int foo(void) PROC                                        ; foo, COMDAT
$LN4:
        sub     rsp, 40                             ; 00000028H
        mov     rax, QWORD PTR Stack * __ptr64 __ptr64 S1
        mov     r8b, 2
        mov     dl, 1
        mov     rcx, rax                   ;; copy RAX to the arg-passing register
        call    QWORD PTR [rax+16]
        xor     eax, eax
        add     rsp, 40                             ; 00000028H
        ret     0
int foo(void) ENDP                                        ; foo

(我以C ++模式编译,因此无需复制github代码即可编写S1 = new(Stack),并使用非常量初始化程序在全局范围内编写它。)

Clang7.0 -O3立即加载到RCX中:

# clang -O3
foo():
        sub     rsp, 40
        mov     rcx, qword ptr [rip + S1]
        mov     dl, 1
        mov     r8b, 2
        call    qword ptr [rcx + 16]          # uses the arg-passing register
        xor     eax, eax
        add     rsp, 40
        ret

奇怪的是,当使用__attribute__((ms_abi))定位Windows ABI时,clang仅决定使用低字节寄存器。在使用默认的Linux调用约定而不是mov esi, 1时,它使用mov sil, 1来避免错误的依赖关系。


或者,如果您正在使用优化,那是因为即使是较旧的MSVC甚至更糟。在那种情况下,尽管您可能尝试使用struct Stack *p = S1局部变量来使编译器一次将其加载到寄存器中并从那里重用,但是您可能无法在C源代码中进行任何操作来对其进行修复。 )