【问题标题】:In this compiler output, why does func(int) use its first arg as a pointer, zeroing 24 bytes of pointed-to memory? The arg isn't a pointer在这个编译器输出中,为什么 func(int) 使用它的第一个 arg 作为指针,将 24 个字节的指向内存归零? arg 不是指针
【发布时间】:2018-06-13 22:07:41
【问题描述】:

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

vector<int> func(int i) { ...}  // C++ source

clang 输出from the Godbolt compiler explorer:

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

这是在 Linux 上编译的,遵循官方的 System V AMD64 ABI。 根据this link,rdi 寄存器用于将第一个参数传递给函数。 所以在这一行

mov rbx, rdi

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

movups xmmword ptr [rbx], xmm0

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

有些东西我弄错了,但我不知道为什么。

【问题讨论】:

  • 对象通过隐藏的第一个参数返回。因此,您的函数看起来更像func(vector&lt;int&gt;* result, int i)rdi 是指向返回结果的指针,iesi(第二个参数槽)中。
  • @Jester,你应该把这个作为答案。
  • 请注意,并非所有对象都返回隐藏指针:ABI 允许您将大多数 16 字节或更少的对象打包到 raxrdx寄存器作为返回值,但std::vector 通常为 24 字节,因此最终使用隐藏指针。

标签: assembly x86-64 calling-convention


【解决方案1】:

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

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

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

我们可以通过检查为返回 1 到 5 个 int 值(因此在这个平台上是 4 到 20 个字节)的 struct 对象的函数生成的代码清楚地看到这一点。 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};
}

clang 6.0 中的following assembly 的结果(但其他编译器的行为类似:

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

基本模式是最多包含 8 个字节,struct 完全在 rax 中返回(包括在 64 位寄存器中打包多个较小的值),对于最多 16 个字节的对象,@使用了 987654339@ 和 rdx1

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

最后,总结一下,我们注意到 sizeof(vector&lt;int&gt;) 在 64 位平台上是 usually 24 bytes,在 Linux 上的主要 C++ 编译器上是 definitely 24 字节 - 所以隐藏指针方法适用于向量.


感谢 Jester 已经以更简短的形式回答了这个问题,in the comments

1223338299434 这样被存储到 64 位寄存器中的奇怪常量只是一种优化:编译器只是将两个 32 位存储组合成一个 64 位常量,如在52ul &lt;&lt; 32 | 42ul 中,结果为223338299434

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-23
    • 2021-12-28
    • 2014-08-13
    • 1970-01-01
    • 2011-08-10
    • 1970-01-01
    相关资源
    最近更新 更多