在这个编译器输出中,为什么func(int)使用它的第一个arg作为指针,将24个字节的指向内存归零?arg不是指针

use*_*145 2 assembly x86-64 calling-convention

我在理解这个汇编代码的作用时遇到了问题(这是一个较大的汇编代码,这是Intel语法):

vector<int> func(int i) { ...}  // C++ source
Run Code Online (Sandbox Code Playgroud)

来自Godbolt编译器资源管理器的 clang输出:

func(int): # @func(int)
    push    rbp
    push    rbx
    push    rax
    mov     ebp, esi
    mov     rbx, rdi
    xorps   xmm0, xmm0
    movups  xmmword ptr [rbx], xmm0
    mov     qword ptr [rbx + 16], 0
Run Code Online (Sandbox Code Playgroud)

这是在Linux上编译的,遵循官方的System V AMD64 ABI.根据此链接,rdi寄存器用于将第一个参数传递给函数.所以在这条线上

mov rbx, rdi
Run Code Online (Sandbox Code Playgroud)

我们将参数的值(在本例中为int)移动到rbx.不久之后,我们做到:

movups xmmword ptr [rbx], xmm0
Run Code Online (Sandbox Code Playgroud)

这是我不明白的.rbx包含参数的值,它是一个int,这里我们将xmm0的内容复制到rbx指向的地址(但是rbx不包含任何地址,只是函数的参数!)

有些事我错了,但我无法弄明白为什么.

Bee*_*ope 5

在Linux和Windows以外的大多数其他64位x86操作系统使用的SysV 64位ABI中,struct或者class返回值raxrdx寄存器中的返回值,或者通过作为第一个参数传递的隐藏指针返回.

两个选项之间的决定主要取决于返回结构的大小:大于16字节的结构通常使用隐藏指针方法,但也有其他因素,我建议这个答案进行更全面的处理.

当使用隐藏指针方法时,我们需要一种方法将此指针传递给函数.在这种情况下,指针的行为就像它是第一个参数(传入rdi)一样,它将其他参数转移到后面的位置2.

我们可以通过检查为返回struct1到5个int值的对象(因此在该平台上4到20个字节)生成的代码来清楚地看到这一点.C++代码:

struct one {
    int x;
};

struct two {
    int x1, x2;
};

struct three {
    int x1, x2, x3;
};

struct four {
    int x1, x2, x3, x4;
};

struct five {
    int x1, x2, x3, x4, x5;
};


one makeOne() {
    return {42};
}

two makeTwo() {
    return {42, 52};
}

three makeThree() {
    return {42, 52, 62};
}

four makeFour() {
    return {42, 52, 62, 72};
}

five makeFive() {
    return {42, 52, 62, 72, 82};
}
Run Code Online (Sandbox Code Playgroud)

结果在6.0 中的以下程序集中clang(但其他编译器的行为类似:

makeOne():                            # @makeOne()
        mov     eax, 42
        ret
makeTwo():                            # @makeTwo()
        movabs  rax, 223338299434
        ret
makeThree():                          # @makeThree()
        movabs  rax, 223338299434
        mov     edx, 62
        ret
makeFour():                           # @makeFour()
        movabs  rax, 223338299434
        movabs  rdx, 309237645374
        ret
.LCPI4_0:
        .long   42                      # 0x2a
        .long   52                      # 0x34
        .long   62                      # 0x3e
        .long   72                      # 0x48
makeFive():                           # @makeFive()
        movaps  xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = [42,52,62,72]
        movups  xmmword ptr [rdi], xmm0
        mov     dword ptr [rdi + 16], 82
        mov     rax, rdi
        ret
Run Code Online (Sandbox Code Playgroud)

基本模式是至多且包括8个字节,则struct在完全返回rax(包括包装在64位寄存器的多个更小的值),以及用于对象最多16个字节都raxrdx使用1.

之后,策略完全改变,我们看到内存写入发生在指向的位置rdi- 这是上面提到的隐藏指针方法.

最后,来包装它全部,我们注意到,sizeof(vector<int>)通常的24个字节的64位平台,而且是绝对的大C++在Linux上的编译器24个字节-这样的隐藏指针的方法适用于矢量.


感谢Jester已经在评论中以更简洁的形式回答了这个问题.

1223338299434这样的奇怪常量被存储到64位寄存器只是一个优化:编译器只是将两个32位存储组合成一个64位常量,就像在52ul << 32 | 42ul其中产生的那样223338299434.

2这与用于传递this成员函数的方法相同:在成员函数返回使用隐藏指针方法传递的值的情况下,隐藏指针首先(in rdi),然后是this指针(in rsi)然后最后是第一个用户提供的参数(通常在rdx- 但这取决于类型).这是一个例子.