如何在 C++ 中实现非静态、非虚方法?

How are non-static, non-virtual methods implemented in C++?

我想知道 C++ 中的方法是如何实现的。我想知道方法是如何“在幕后”实施的。 所以,我制作了一个简单的 C++ 程序,它有一个 class,带有 1 个非静态字段和 1 个非静态、非虚拟方法。

然后我在主函数中实例化了class并调用了方法。我使用了 objdump -d 选项来查看该程序的 CPU 指令。我有一个 x86-64 处理器。 这是代码:

#include<stdio.h>


class TestClass {
public:
   int x;
   int xPlus2(){
       return x + 2;
   }
};

int main(){
    TestClass tc1 = {5};
    int variable = tc1.xPlus2();
    printf("%d \n", variable);
    return 0;
}

这里是方法的说明 xPlus2:

  0000000000402c30 <_ZN9TestClass6xPlus2Ev>:
  402c30:   55                      push   %rbp
  402c31:   48 89 e5                mov    %rsp,%rbp
  402c34:   48 89 4d 10             mov    %rcx,0x10(%rbp)
  402c38:   48 8b 45 10             mov    0x10(%rbp),%rax
  402c3c:   8b 00                   mov    (%rax),%eax
  402c3e:   83 c0 02                add    [=11=]x2,%eax
  402c41:   5d                      pop    %rbp
  402c42:   c3                      retq   
  402c43:   90                      nop
  402c44:   90                      nop
  402c45:   90                      nop
  402c46:   90                      nop
  402c47:   90                      nop
  402c48:   90                      nop
  402c49:   90                      nop
  402c4a:   90                      nop
  402c4b:   90                      nop
  402c4c:   90                      nop
  402c4d:   90                      nop
  402c4e:   90                      nop
  402c4f:   90                      nop

如果我没理解错的话,这些指令可以只用3条指令代替,因为我相信我不需要使用堆栈,我认为编译器使用它是多余的:

mov (%rcx), eax
add , eax
retq

然后也许我仍然需要大量的 nop 指令用于同步目的或诸如此类的目的。如果您查看 CPU 指令,看起来 x 字段 的值存储在内存中 rcx 寄存器 的位置] 成立。稍后您将看到其余的 CPU 说明。跟踪这里发生的事情对我来说有点困难(尤其是 _main 函数的调用发生了什么),我什至不知道汇编的哪些部分是重要的。编译器生成了 main 函数(如我所料),但随后它也生成了从 main 调用的 _main 函数,这两者之间也有一些奇怪的函数。 以下是我认为可能有趣的程序集的其他部分:

  0000000000401550 <main>:
  401550:   55                      push   %rbp
  401551:   48 89 e5                mov    %rsp,%rbp
  401554:   48 83 ec 30             sub    [=13=]x30,%rsp
  401558:   e8 e3 00 00 00          callq  401640 <__main>
  40155d:   c7 45 f8 05 00 00 00    movl   [=13=]x5,-0x8(%rbp)
  401564:   48 8d 45 f8             lea    -0x8(%rbp),%rax
  401568:   48 89 c1                mov    %rax,%rcx
  40156b:   e8 c0 16 00 00          callq  402c30 <_ZN9TestClass6xPlus2Ev>
  401570:   89 45 fc                mov    %eax,-0x4(%rbp)
  401573:   8b 45 fc                mov    -0x4(%rbp),%eax
  401576:   89 c2                   mov    %eax,%edx
  401578:   48 8d 0d 81 2a 00 00    lea    0x2a81(%rip),%rcx        # 404000 <.rdata>
  40157f:   e8 ec 14 00 00          callq  402a70 <printf>
  401584:   b8 00 00 00 00          mov    [=13=]x0,%eax
  401589:   48 83 c4 30             add    [=13=]x30,%rsp
  40158d:   5d                      pop    %rbp
  40158e:   c3                      retq   
  40158f:   90                      nop

0000000000401590 <__do_global_dtors>:
  401590:   48 83 ec 28             sub    [=13=]x28,%rsp
  401594:   48 8b 05 75 1a 00 00    mov    0x1a75(%rip),%rax        # 403010 <p.93846>
  40159b:   48 8b 00                mov    (%rax),%rax
  40159e:   48 85 c0                test   %rax,%rax
  4015a1:   74 1d                   je     4015c0 <__do_global_dtors+0x30>
  4015a3:   ff d0                   callq  *%rax
  4015a5:   48 8b 05 64 1a 00 00    mov    0x1a64(%rip),%rax        # 403010 <p.93846>
  4015ac:   48 8d 50 08             lea    0x8(%rax),%rdx
  4015b0:   48 8b 40 08             mov    0x8(%rax),%rax
  4015b4:   48 89 15 55 1a 00 00    mov    %rdx,0x1a55(%rip)        # 403010 <p.93846>
  4015bb:   48 85 c0                test   %rax,%rax
  4015be:   75 e3                   jne    4015a3 <__do_global_dtors+0x13>
  4015c0:   48 83 c4 28             add    [=13=]x28,%rsp
  4015c4:   c3                      retq   
  4015c5:   90                      nop
  4015c6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  4015cd:   00 00 00 

00000000004015d0 <__do_global_ctors>:
  4015d0:   56                      push   %rsi
  4015d1:   53                      push   %rbx
  4015d2:   48 83 ec 28             sub    [=13=]x28,%rsp
  4015d6:   48 8b 0d 23 2d 00 00    mov    0x2d23(%rip),%rcx        # 404300 <.refptr.__CTOR_LIST__>
  4015dd:   48 8b 11                mov    (%rcx),%rdx
  4015e0:   83 fa ff                cmp    [=13=]xffffffff,%edx
  4015e3:   89 d0                   mov    %edx,%eax
  4015e5:   74 39                   je     401620 <__do_global_ctors+0x50>
  4015e7:   85 c0                   test   %eax,%eax
  4015e9:   74 20                   je     40160b <__do_global_ctors+0x3b>
  4015eb:   89 c2                   mov    %eax,%edx
  4015ed:   83 e8 01                sub    [=13=]x1,%eax
  4015f0:   48 8d 1c d1             lea    (%rcx,%rdx,8),%rbx
  4015f4:   48 29 c2                sub    %rax,%rdx
  4015f7:   48 8d 74 d1 f8          lea    -0x8(%rcx,%rdx,8),%rsi
  4015fc:   0f 1f 40 00             nopl   0x0(%rax)
  401600:   ff 13                   callq  *(%rbx)
  401602:   48 83 eb 08             sub    [=13=]x8,%rbx
  401606:   48 39 f3                cmp    %rsi,%rbx
  401609:   75 f5                   jne    401600 <__do_global_ctors+0x30>
  40160b:   48 8d 0d 7e ff ff ff    lea    -0x82(%rip),%rcx        # 401590 <__do_global_dtors>
  401612:   48 83 c4 28             add    [=13=]x28,%rsp
  401616:   5b                      pop    %rbx
  401617:   5e                      pop    %rsi
  401618:   e9 f3 fe ff ff          jmpq   401510 <atexit>
  40161d:   0f 1f 00                nopl   (%rax)
  401620:   31 c0                   xor    %eax,%eax
  401622:   eb 02                   jmp    401626 <__do_global_ctors+0x56>
  401624:   89 d0                   mov    %edx,%eax
  401626:   44 8d 40 01             lea    0x1(%rax),%r8d
  40162a:   4a 83 3c c1 00          cmpq   [=13=]x0,(%rcx,%r8,8)
  40162f:   4c 89 c2                mov    %r8,%rdx
  401632:   75 f0                   jne    401624 <__do_global_ctors+0x54>
  401634:   eb b1                   jmp    4015e7 <__do_global_ctors+0x17>
  401636:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40163d:   00 00 00 

0000000000401640 <__main>:
  401640:   8b 05 ea 59 00 00       mov    0x59ea(%rip),%eax        # 407030 <initialized>
  401646:   85 c0                   test   %eax,%eax
  401648:   74 06                   je     401650 <__main+0x10>
  40164a:   c3                      retq   
  40164b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  401650:   c7 05 d6 59 00 00 01    movl   [=13=]x1,0x59d6(%rip)        # 407030 <initialized>
  401657:   00 00 00 
  40165a:   e9 71 ff ff ff          jmpq   4015d0 <__do_global_ctors>
  40165f:   90                      nop

我想你要找的是这些说明:

 40155d:   c7 45 f8 05 00 00 00    movl   [=10=]x5,-0x8(%rbp)
 401564:   48 8d 45 f8             lea    -0x8(%rbp),%rax
 401568:   48 89 c1                mov    %rax,%rcx
 40156b:   e8 c0 16 00 00          callq  402c30 <_ZN9TestClass6xPlus2Ev>
 401570:   89 45 fc                mov    %eax,-0x4(%rbp)

这些与 main 中的代码匹配:

TestClass tc1 = {5};
int variable = tc1.xPlus2();
  • 在地址 40155d 处,字段 tc1.x 初始化为值 5。
  • 在地址 401564 处,指向 tc1 的指针被加载到寄存器 %rax
  • 地址 401568 指向 tc1 的指针被复制到寄存器 %rcx
  • 地址40156b是方法tc1.xPlus2()
  • 的调用
  • 地址 401570 结果存储在 variable

您的观察大部分是正确的。 rcx 持有指向调用该方法的对象的 this 指针。 x 存储在 this 指针指向的第一个内存区域,因此这就是 rcx 被取消引用并将结果添加到的原因。调用者有责任在调用函数之前确保 rcx 是对象的地址。我们可以看到 main 通过将其设置为堆栈帧中的地址来准备 rcx。你是对的,编译器在这里产生了低效的代码并且不需要使用堆栈。使用更高的优化级别 -O1-O2-O3 进行编译可能会解决该问题。这些更高的优化也可能会摆脱 nops,因为它们用于函数对齐。您几乎可以忽略 __main。用于libc初始化。