从汇编角度看C++的函数

C++中的函数

简单的汇编知识

C++中函数参数如何传递给函数

1. 普通函数

c++ 复制代码
#include <iostream>
using namespace std;

int func(int num)
{
  int x = 1;
  int y = 2;
  num += 2;
  return num;
}

int main()
{
  func(10);
}

查看func函数的汇编代码:

assembly 复制代码
Dump of assembler code for function func(int):
=> 0x0000555555555169 <+0>:     endbr64 
   0x000055555555516d <+4>:     push   rbp
   0x000055555555516e <+5>:     mov    rbp,rsp
   0x0000555555555171 <+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x0000555555555174 <+11>:    mov    DWORD PTR [rbp-0x8],0x1
   0x000055555555517b <+18>:    mov    DWORD PTR [rbp-0x4],0x2
   0x0000555555555182 <+25>:    add    DWORD PTR [rbp-0x14],0x2
   0x0000555555555186 <+29>:    mov    eax,DWORD PTR [rbp-0x14]
   0x0000555555555189 <+32>:    pop    rbp
   0x000055555555518a <+33>:    ret    
End of assembler dump.

大致分析一下这段代码:

  • 最开始先将在func函数中可能修改的寄存器值先保存在栈中,等最后出栈恢复,这样能保证执行func后前后的值一致。

    assembly 复制代码
    0x000055555555516d <+4>:     push   rbp
    
    ........
    
    0x0000555555555189 <+32>:    pop    rbp
  • 然后将edi(也就是num的值)保存在栈上,因为x和y是局部变量,所以它们也是存在栈中的。

    assembly 复制代码
     0x0000555555555171 <+8>:     mov    DWORD PTR [rbp-0x14],edi
     0x0000555555555174 <+11>:    mov    DWORD PTR [rbp-0x8],0x1
     0x000055555555517b <+18>:    mov    DWORD PTR [rbp-0x4],0x2
  • 然后就是一些计算逻辑。

  • 然后将返回值通过寄存器eax返回。

    assembly 复制代码
    0x0000555555555186 <+29>:    mov    eax,DWORD PTR [rbp-0x14]
  • 最后跳转到func函数的下一条指令

    assembly 复制代码
    0x000055555555518a <+33>:    ret 

2. 类成员函数

c++ 复制代码
#include <iostream>
using namespace std;

class Room
{
public:
  int age;

  void func()
  {
    int a = 11;
    int b = 12;
  }
};

int main()
{
  Room room;
  room.func();
}

step1

assembly 复制代码
   0x555555555195 <main+12>    mov    rax, qword ptr fs:[0x28]
   0x55555555519e <main+21>    mov    qword ptr [rbp - 8], rax
   0x5555555551a2 <main+25>    xor    eax, eax
   0x5555555551a4 <main+27>    lea    rax, [rbp - 0xc]
   0x5555555551a8 <main+31>    mov    rdi, rax
 ► 0x5555555551ab <main+34>    call   Room::func()                <Room::func()>
        rdi: 0x7fffffffe4e4 ◂--- 0x4b9ba90000007fff

其中 rdi 寄存器保存的是对象room的地址,也是我们常说的this指针。

虽然类成员函数func从表面上看没有参数,但是其实还是会有一个this指针被默认传递进去,也就是这里的rdi中的值。

step2

assembly 复制代码
Dump of assembler code for function Room::func():
=> 0x0000555555555232 <+0>:     endbr64 
   0x0000555555555236 <+4>:     push   rbp
   0x0000555555555237 <+5>:     mov    rbp,rsp
   0x000055555555523a <+8>:     mov    QWORD PTR [rbp-0x18],rdi
   0x000055555555523e <+12>:    mov    DWORD PTR [rbp-0x8],0xb
   0x0000555555555245 <+19>:    mov    DWORD PTR [rbp-0x4],0xc
   0x000055555555524c <+26>:    nop
   0x000055555555524d <+27>:    pop    rbp
   0x000055555555524e <+28>:    ret    
End of assembler dump.

局部变量a、b和rdi在栈中的位置图:

3. 普通静态函数

c++ 复制代码
#include <iostream>
using namespace std;

static void func()
{
  int a = 11;
  int b = 12;
}

int main()
{
  func();
}

汇编代码:

assembly 复制代码
Dump of assembler code for function func():
=> 0x0000555555555169 <+0>:     endbr64 
   0x000055555555516d <+4>:     push   rbp
   0x000055555555516e <+5>:     mov    rbp,rsp
   0x0000555555555171 <+8>:     mov    DWORD PTR [rbp-0x8],0xb
   0x0000555555555178 <+15>:    mov    DWORD PTR [rbp-0x4],0xc
   0x000055555555517f <+22>:    nop
   0x0000555555555180 <+23>:    pop    rbp
   0x0000555555555181 <+24>:    ret    
End of assembler dump.

4. 类中的静态函数

c++ 复制代码
#include <iostream>
using namespace std;

class Room
{
public:
  int age;

  static void func()
  {
    int a = 11;
    int b = 12;
  }
};

int main()
{
  Room room;
  room.func();
  Room::func();
}

func的汇编代码:

assembly 复制代码
Dump of assembler code for function Room::func():
=> 0x00005555555551ec <+0>:     endbr64 
   0x00005555555551f0 <+4>:     push   rbp
   0x00005555555551f1 <+5>:     mov    rbp,rsp
   0x00005555555551f4 <+8>:     mov    DWORD PTR [rbp-0x8],0xb
   0x00005555555551fb <+15>:    mov    DWORD PTR [rbp-0x4],0xc
   0x0000555555555202 <+22>:    nop
   0x0000555555555203 <+23>:    pop    rbp
   0x0000555555555204 <+24>:    ret    
End of assembler dump.

从这里我们看到静态成员函数没有this指针

重点分析room.func();Room::func();

assembly 复制代码
   0x555555555169 <main>                                                     endbr64 
   0x55555555516d <main+4>                                                   push   rbp
   0x55555555516e <main+5>                                                   mov    rbp, rsp
   0x555555555171 <main+8>                                                   sub    rsp, 0x10
 ► 0x555555555175 <main+12>                                                  call   Room::func()                <Room::func()>
 
   0x55555555517a <main+17>                                                  call   Room::func()                <Room::func()>

   23 int main()
   24 {
   25   Room room;
 ► 26   room.func();
   27   Room::func();
   28 }
assembly 复制代码
   0x555555555169 <main>                                                     endbr64 
   0x55555555516d <main+4>                                                   push   rbp
   0x55555555516e <main+5>                                                   mov    rbp, rsp
   0x555555555171 <main+8>                                                   sub    rsp, 0x10
   0x555555555175 <main+12>                                                  call   Room::func()                <Room::func()>
 
 ► 0x55555555517a <main+17>                                                  call   Room::func()                <Room::func()>
 
   23 int main()
   24 {
   25   Room room;
   26   room.func();
 ► 27   Room::func();
   28 }

我们看到这两种写法在汇编代码的层次上都是一一样的,因为func是静态的,所以它是属于类的,而不是类对象的。所以两种方式都可以,底层都是直接调用func就行了。

5. 虚函数

彻底搞懂C++多态

普通函数和类成员函数的汇编代码对比

c++ 复制代码
#include <iostream>
using namespace std;

class Room
{
public:
  int age;

  void func()
  {
    int a = 11;
    int b = 12;
    age = 10;
  }
};

void func(Room *room)
{
  int a = 11;
  int b = 12;
  room->age = 10;
}

int main()
{
  Room room;
  room.func();

  func(&room);
}
  • 分析 room.func();
assembly 复制代码
Dump of assembler code for function Room::func():
=> 0x0000555555555264 <+0>:     endbr64 
   0x0000555555555268 <+4>:     push   rbp
   0x0000555555555269 <+5>:     mov    rbp,rsp
   0x000055555555526c <+8>:     mov    QWORD PTR [rbp-0x18],rdi
   0x0000555555555270 <+12>:    mov    DWORD PTR [rbp-0x8],0xb
   0x0000555555555277 <+19>:    mov    DWORD PTR [rbp-0x4],0xc
   0x000055555555527e <+26>:    mov    rax,QWORD PTR [rbp-0x18]
   0x0000555555555282 <+30>:    mov    DWORD PTR [rax],0xa
   0x0000555555555288 <+36>:    nop
   0x0000555555555289 <+37>:    pop    rbp
   0x000055555555528a <+38>:    ret    
End of assembler dump.
  • 分析 func(&room);
assembly 复制代码
Dump of assembler code for function func(Room*):
=> 0x0000555555555189 <+0>:     endbr64 
   0x000055555555518d <+4>:     push   rbp
   0x000055555555518e <+5>:     mov    rbp,rsp
   0x0000555555555191 <+8>:     mov    QWORD PTR [rbp-0x18],rdi
   0x0000555555555195 <+12>:    mov    DWORD PTR [rbp-0x8],0xb
   0x000055555555519c <+19>:    mov    DWORD PTR [rbp-0x4],0xc
   0x00005555555551a3 <+26>:    mov    rax,QWORD PTR [rbp-0x18]
   0x00005555555551a7 <+30>:    mov    DWORD PTR [rax],0xa
   0x00005555555551ad <+36>:    nop
   0x00005555555551ae <+37>:    pop    rbp
   0x00005555555551af <+38>:    ret    
End of assembler dump.

我们看到两者的汇编代码一摸一样,这时为什么呢?

C++设计的时候有一个要求:要求对普通成员函数的调用不应该比全局函数效率差;

基于这种设计要求,编译器内部实际上是将对成员函数的调用转换成了对全局函数的调用。

成员函数有独立的地址,是和类相关的,并且成员函数的地址,是在编译的时候就确定好了的。

搞定🤝。

相关推荐
luj_17684 小时前
残熵算法实时化三大瓶颈突破
c语言·开发语言·网络·经验分享·算法
不听话坏4 小时前
Ignition篇(下 一) 动态执行前的事情
开发语言·前端·javascript
likeyi074 小时前
require 和 import的区别
开发语言·前端
远离UE45 小时前
UE5 compute shader 原子加
开发语言·c++·ue5
C+-C资深大佬5 小时前
C++ 显式类型转换详解:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
KaMeidebaby6 小时前
卡梅德生物技术快报|抗体亲和力成熟工业化调控新机制:差异性浆细胞增殖工艺优化思路
java·开发语言·人工智能·算法·机器学习·架构·spark
luj_17686 小时前
心形曲线轨迹控制三大关键技术
c语言·开发语言·c++·经验分享·算法
取地址符7 小时前
C++学习笔记(基于learn-cxx)(1)
c++·经验分享·笔记·学习
Mininglamp_27187 小时前
Claude Code 封禁中国开发者之后:本地 AI 编程工具的替代方案实测
开发语言·人工智能·windows·开源软件·ai-native
思麟呀7 小时前
C++17(三)if constexpr+折叠表达式
开发语言·c++