C++中的函数
简单的汇编知识
1. 普通函数
#include <iostream>
using namespace std;
int func(int num)
{
int x = 1;
int y = 2;
num += 2;
return num;
}
int main()
{
func(10);
}
查看func函数的汇编代码:
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后前后的值一致。
0x000055555555516d <+4>: push rbp ........ 0x0000555555555189 <+32>: pop rbp
-
然后将edi(也就是num的值)保存在栈上,因为x和y是局部变量,所以它们也是存在栈中的。
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返回。
0x0000555555555186 <+29>: mov eax,DWORD PTR [rbp-0x14]
-
最后跳转到func函数的下一条指令
0x000055555555518a <+33>: ret
2. 类成员函数
#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
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
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. 普通静态函数
#include <iostream>
using namespace std;
static void func()
{
int a = 11;
int b = 12;
}
int main()
{
func();
}
汇编代码:
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. 类中的静态函数
#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的汇编代码:
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();
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 }
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. 虚函数
普通函数和类成员函数的汇编代码对比
#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();
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);
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++设计的时候有一个要求:要求对普通成员函数的调用不应该比全局函数效率差;
基于这种设计要求,编译器内部实际上是将对成员函数的调用转换成了对全局函数的调用。
成员函数有独立的地址,是和类相关的,并且成员函数的地址,是在编译的时候就确定好了的。
搞定🤝。