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

标签 assembly x86-64 calling-convention

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

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 不包含任何地址,只包含函数的参数!)

我犯了一些错误,但我不明白为什么。

最佳答案

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

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

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

通过检查返回 struct 的函数生成的代码,我们可以清楚地看到这一点。 1 到 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};
}

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

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 个字节(含 8 个字节),即 struct完全返回于 rax (包括在 64 位寄存器中打包多个较小的值),对于最多 16 字节的对象,raxrdx使用1

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

最后,总结一下,我们注意到 sizeof(vector<int>)usually 24 bytes在 64 位平台上,是 definitely Linux 上的主要 C++ 编译器上为 24 个字节 - 因此隐藏指针方法适用于向量。


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

1223338299434 这样奇怪的常量存储到 64 位寄存器中的只是一种优化:编译器只是将两个 32 位存储组合成一个 64 位常量,如 52ul << 32 | 42ul 中所示。结果是 223338299434 .

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

关于assembly - 在此编译器输出中,为什么 func(int) 使用其第一个 arg 作为指针,将指向内存的 24 字节归零? arg 不是指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50846969/

相关文章:

c - 如何使共享库成为可执行文件

assembly - 汇编程序中的重定位截断错误

linux - 为什么 RCX 不用于将参数传递给系统调用,而是用 R10 代替?

javascript - 由 `new` 构造函数创建的函数对象是否被视为 JavaScript 中的可变对象?

assembly - 在 MIPS 中,什么是 HI 和 LO

assembly - x86 汇编中符号扩展中的前导零?

c - MARS MIPS 和结构节点

c - 尝试从 C 生成汇编代码

assembly - 最小的可执行程序 (x86-64)

c++ - 检查函数指针类型的调用约定