为什么附加的指针参数在汇编中消失了?

Why did additional pointer arguments disappear in assembly?

C代码:

void PtrArg1(int* a,int* b,int* c, int* d, int* e, int* f)
{
    return;
}

void PtrArg2(int* a,int* b,int* c, int* d, int* e, int* f, int* g, int* h)
{
    return;
}

编译

gcc -c -m64 -o basics basics.c -O0

运行

objdump -d basics -M intel -r

然后导致以下反汇编(Intel 语法):

000000000000000b <PtrArg1>:
   b:   f3 0f 1e fa             endbr64 
   f:   55                      push   rbp
  10:   48 89 e5                mov    rbp,rsp
  13:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  17:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  1b:   48 89 55 e8             mov    QWORD PTR [rbp-0x18],rdx
  1f:   48 89 4d e0             mov    QWORD PTR [rbp-0x20],rcx
  23:   4c 89 45 d8             mov    QWORD PTR [rbp-0x28],r8
  27:   4c 89 4d d0             mov    QWORD PTR [rbp-0x30],r9
  2b:   90                      nop
  2c:   5d                      pop    rbp
  2d:   c3                      ret    

000000000000002e <PtrArg2>:
  2e:   f3 0f 1e fa             endbr64 
  32:   55                      push   rbp
  33:   48 89 e5                mov    rbp,rsp
  36:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  3a:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  3e:   48 89 55 e8             mov    QWORD PTR [rbp-0x18],rdx
  42:   48 89 4d e0             mov    QWORD PTR [rbp-0x20],rcx
  46:   4c 89 45 d8             mov    QWORD PTR [rbp-0x28],r8
  4a:   4c 89 4d d0             mov    QWORD PTR [rbp-0x30],r9
  4e:   90                      nop
  4f:   5d                      pop    rbp
  50:   c3                      ret 

PtrArg1PtrArg2 的参数数量不同,但两者的汇编指令相同。为什么?

这是调用约定造成的(System V AMD64 ABI,current version 1.0)。前六个参数在整数寄存器中传递,所有其他参数被压入堆栈。

执行到位置 PtrArg2+0x4e 后,您将获得以下堆栈布局:

+----------+-----------------+
|  offset  |     content     |
+----------+-----------------+
| rbp-0x30 | f               |
| rbp-0x28 | e               |
| rbp-0x20 | d               |
| rbp-0x18 | c               |
| rbp-0x10 | b               |
| rbp-0x8  | a               |
| rbp+0x0  | saved rbp value |
| rbp+0x8  | return address  |
| rbp+0x10 | g               |
| rbp+0x18 | h               |
+----------+-----------------+

由于 gh 是由调用者推送的,因此您可以对这两个函数进行相同的反汇编。对于来电者

void Caller()
{
    PtrArg2(1, 2, 3, 4, 5, 6, 7, 8);
}

(为清楚起见,我省略了必要的转换)我们将得到以下反汇编:

Caller():
    push    rbp
    mov     rbp, rsp
    push    8
    push    7
    mov     r9d, 6
    mov     r8d, 5
    mov     ecx, 4
    mov     edx, 3
    mov     esi, 2
    mov     edi, 1
    call    PtrArg2
    add     rsp, 16
    nop
    leave
    ret

(see compiler explorer)

在调用 PtrArg2.

之前,参数 h = 8g = 7 被压入堆栈

消失?您期望编译器发出 asm 指令来执行的函数对它们做什么?

您实际上 return;void 函数中的唯一语句,因此除了 ret 之外,该函数不需要做任何事情。如果您使用像 -O2 这样的正常优化级别进行编译,这就是您所得到的。调试模式的代码通常看起来没什么意思,而且充满了冗余/无用的东西。

您看到某些参数的任何说明的唯一原因是您在调试模式下编译,即默认优化级别 -O0,反优化调试模式。 每个 C 对象(register 本地对象除外)都有一个内存地址,调试模式确保该值确实存在于内存中 before/after 每个 C 语句。这意味着在函数入口处将寄存器参数溢出到堆栈。 Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?

x86-64 System V ABI的调用约定在寄存器中传递前 6 个整数参数,其余在堆栈中。堆栈参数已经有内存地址;编译器不会发出代码将它们复制到 return 地址下方的其他本地变量旁边;那将毫无意义。被调用者 "owns" 它自己的堆栈参数,即它可以将新值存储到调用者写入 args 的堆栈 space 中,因此 space 可以是 args 的真实地址,即使功能是修改它们。