简介:我们从预处理、编译、汇编、编译、链接、装载一路走到现在,一直在c语言的层面分析,原因还是这些知识太苦涩,理解时需要看下具体的反汇编代码,且c++的反汇编更难理解些,所以,c语言的入手比c++入手更简单和更好理解些。在这个系列的结尾,我们从汇编层面分析下c++ new的过程、c++类的初始化过程、类的成员函数调用过程。另外再从汇编层面看下c语言的指针是怎么工作的。
一、汇编与指针
1、普通指针
普通指针:普通变量的指针(除char)
- demo
#include <stdio.h>
void show(int *ptr)
{
int temp = *ptr;
printf("the number is: %d\n",temp);
}
int main()
{
int a = 3;
int * ptr = &a;
show(ptr);
return 0;
}
- 编译、执行、反汇编
[root@localhost test01_putongzhizhen]# gcc main.c
[root@localhost test01_putongzhizhen]# ./a.out
the number is: 3
[root@localhost test01_putongzhizhen]# objdump -d -x ./a.out
000000000040052d <show>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 20 sub $0x20,%rsp
400535: 48 89 7d e8 mov %rdi,-0x18(%rbp)
400539: 48 8b 45 e8 mov -0x18(%rbp),%rax
40053d: 8b 00 mov (%rax),%eax
40053f: 89 45 fc mov %eax,-0x4(%rbp)
400542: 8b 45 fc mov -0x4(%rbp),%eax
400545: 89 c6 mov %eax,%esi
400547: bf 20 06 40 00 mov $0x400620,%edi
40054c: b8 00 00 00 00 mov $0x0,%eax
400551: e8 ba fe ff ff callq 400410 <printf@plt>
400556: c9 leaveq
400557: c3 retq
0000000000400558 <main>:
400558: 55 push %rbp
400559: 48 89 e5 mov %rsp,%rbp
40055c: 48 83 ec 10 sub $0x10,%rsp
400560: c7 45 f4 03 00 00 00 movl $0x3,-0xc(%rbp)
400567: 48 8d 45 f4 lea -0xc(%rbp),%rax
40056b: 48 89 45 f8 mov %rax,-0x8(%rbp)
40056f: 48 8b 45 f8 mov -0x8(%rbp),%rax
400573: 48 89 c7 mov %rax,%rdi
400576: e8 b2 ff ff ff callq 40052d <show>
40057b: b8 00 00 00 00 mov $0x0,%eax
400580: c9 leaveq
400581: c3 retq
400582: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
400589: 00 00 00
40058c: 0f 1f 40 00 nopl 0x0(%rax
- main
- 在 x86 汇编中,括号 () 被用来表示寻址,也就是通过某个地址访问该地址存储的内容。例如,(%rax) 表示使用寄存器 %rax 存储的地址所对应的内存单元的值。
- LEA(Load Effective Address,加载有效地址)是一种 x86 汇编指令,用于将一个内存地址的有效地址(即相对于段基址的偏移量)加载到寄存器中。它的语法如下:
LEA destination, source
其中,destination 是目标操作数,source 是源操作数。指令的作用是将 source 的地址计算出来,并将其存储在 destination 中。
x86-64 汇编的 main 函数,其作用是调用另一个函数 show 并将其返回值存储在 %eax 寄存器中,并最终返回 0。
下面是各条指令的解释:
1. push %rbp:将当前栈帧的基址寄存器 %rbp 压入栈中,为函数的局部变量和参数预留空间。
2. mov %rsp, %rbp:将栈顶指针寄存器 %rsp 的值赋给 %rbp,以此建立栈帧。
3. sub $0x10, %rsp:在栈上分配 16 字节的内存,以此存储该函数的局部变量和参数。
4. movl $0x3,-0xc(%rbp):将立即数 3 存储到相对于 %rbp 偏移量为 -0xc 的内存位置中,相当于声明了一个名为 x 的 int 变量并且初始化为 3。
5. lea -0xc(%rbp),%rax:计算出相对于 %rbp 偏移量为 -0xc 的内存地址,并将其存储到通用寄存器 %rax 中。
6. mov %rax,-0x8(%rbp):将 %rax 中存储的地址存储到相对于 %rbp 偏移量为 -0x8 的内存位置中,相当于声明了一个名为 p 的指针变量并且将其初始化为指向 x 的地址。
8. mov -0x8(%rbp),%rax:将相对于 %rbp 偏移量为 -0x8 的内存地址中存储的值(即指向 x 的地址)存储到通用寄存器 %rax 中。
9. mov %rax,%rdi:将 %rax 中存储的地址存储到参数寄存器 %rdi 中,准备传递给 show 函数。
10. callq 40052d <show>:调用 show 函数,并将其返回值存储在 %eax 寄存器中。
11. mov $0x0,%eax:将立即数 0 存储到 %eax 中,作为函数的返回值。
12. leaveq:恢复栈帧基址并弹出栈顶指针,表示该函数的执行结束。
13. retq:返回到调用者的地址,并将栈指针调整至原来的位置。
- show
这是一段x86_64汇编代码,其函数名为show,具体内容如下:
首先,该函数执行了一些准备工作,包括将rbp寄存器的值压入栈中(push %rbp),将rsp寄存器的值存储在rbp寄存器中(mov %rsp,%rbp),以及分配了20个字节的空间用于存储局部变量(sub $0x20,%rsp)。
然后,函数将传入的第一个参数(即调用该函数时传进来的指针)存储在函数栈帧中的-0x18(%rbp)位置(mov %rdi,-0x18(%rbp))。
接着,函数将存储在-0x18(%rbp)位置的指针赋值给rax寄存器(mov -0x18(%rbp),%rax)。
然后,函数从rax寄存器中取出指针指向的内存地址处的值(mov (%rax),%eax)并将其存储在函数栈帧中的-0x4(%rbp)位置。
函数接着从-0x4(%rbp)位置取出该值(mov -0x4(%rbp),%eax),并将其保存在esi寄存器中(mov %eax,%esi)。
然后,函数将另一个值(即0x400620)存储在edi寄存器中(mov $0x400620,%edi)。
接着,函数调用printf函数(callq 400410 printf@plt)并将参数传递给它(第一个参数esi,第二个参数edi)。这里的printf函数是程序中另一个地方定义的,不在本段代码范围内。
最后,函数清除栈帧(leaveq)并返回(retq)。
总之,该函数的作用是从传入的指针指向的内存位置中读取一个值,并将其作为参数传递给printf函数进行输出。
2、字符串常量指针
字符串常量指针:字符串常量本身就是一个地址!
- demo
#include <stdio.h>
//#include<cstring>
void show(char *ptr)
{
char *temp ;
temp = ptr;
printf("the char * is: %s\n",ptr);
}
int main()
{
char chr[20] = "hello world!\n";
char * ptr = chr;
show(ptr);
return 0;
}
- 编译、执行、反汇编
大家可以参考上面的,自己一句句的分析下。
[root@localhost test01_zz_str]# gcc main.c
[root@localhost test01_zz_str]# ./a.out
the char * is: hello world!
[root@localhost test01_zz_str]# objdump -d -x ./a.out
000000000040052d <show>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 20 sub $0x20,%rsp
400535: 48 89 7d e8 mov %rdi,-0x18(%rbp)
400539: 48 8b 45 e8 mov -0x18(%rbp),%rax
40053d: 48 89 45 f8 mov %rax,-0x8(%rbp)
400541: 48 8b 45 e8 mov -0x18(%rbp),%rax
400545: 48 89 c6 mov %rax,%rsi
400548: bf 30 06 40 00 mov $0x400630,%edi
40054d: b8 00 00 00 00 mov $0x0,%eax
400552: e8 b9 fe ff ff callq 400410 <printf@plt>
400557: c9 leaveq
400558: c3 retq
0000000000400559 <main>:
400559: 55 push %rbp
40055a: 48 89 e5 mov %rsp,%rbp
40055d: 48 83 ec 20 sub $0x20,%rsp
400561: 48 b8 68 65 6c 6c 6f movabs $0x6f77206f6c6c6568,%rax
400568: 20 77 6f
40056b: 48 89 45 e0 mov %rax,-0x20(%rbp)
40056f: 48 b8 72 6c 64 21 0a movabs $0xa21646c72,%rax
400576: 00 00 00
400579: 48 89 45 e8 mov %rax,-0x18(%rbp)
40057d: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%rbp)
400584: 48 8d 45 e0 lea -0x20(%rbp),%rax
400588: 48 89 45 f8 mov %rax,-0x8(%rbp)
40058c: 48 8b 45 f8 mov -0x8(%rbp),%rax
400590: 48 89 c7 mov %rax,%rdi
400593: e8 95 ff ff ff callq 40052d <show>
400598: b8 00 00 00 00 mov $0x0,%eax
40059d: c9 leaveq
40059e: c3 retq
40059f: 90 nop
二、汇编与new
1、demo
#include <iostream>
#include<cstring>
using namespace std;
class Person
{
public:
Person(int age)
{
m_age = age;
printf("Person 构造完成\n");
}
~Person(){printf("Person 析构完成\n");}
public:
void speak()
{
// cout<<"my name is "<<m_name<<"my age is "<<m_age<<endl;
printf("my name is %s,\nmy age is %d\n",m_name,m_age);
};
private:
const char* m_name = "junxue" ;
int m_age;
};
int main()
{
Person* jun = new Person(18);
// use
jun->speak();
delete jun;
return 0;
}
- 编译、执行、反汇编
备注:为了分析主要的几个点,我删除了部分反汇编的代码。
[root@localhost test04new]# g++ -std=c++11 main.cpp
[root@localhost test04new]# ./a.out
Person 构造完成
my name is junxue,
my age is 18
Person 析构完成
[root@localhost test04new]# objdump -d -x ./a.out
00000000004006e0 <_ZdlPv@plt>:
4006e0: ff 25 4a 09 20 00 jmpq *0x20094a(%rip) # 601030 <_ZdlPv@GLIBCXX_3.4>
4006e6: 68 03 00 00 00 pushq $0x3
4006eb: e9 b0 ff ff ff jmpq 4006a0 <.plt>
0000000000400740 <_Znwm@plt>:
400740: ff 25 1a 09 20 00 jmpq *0x20091a(%rip) # 601060 <_Znwm@GLIBCXX_3.4>
400746: 68 09 00 00 00 pushq $0x9
40074b: e9 50 ff ff ff jmpq 4006a0 <.plt>
000000000040084d <main>:
40084d: 55 push %rbp
40084e: 48 89 e5 mov %rsp,%rbp
400851: 41 54 push %r12
400853: 53 push %rbx
400854: 48 83 ec 10 sub $0x10,%rsp
400858: bf 10 00 00 00 mov $0x10,%edi
40085d: e8 de fe ff ff callq 400740 <_Znwm@plt>
400862: 48 89 c3 mov %rax,%rbx
400865: be 12 00 00 00 mov $0x12,%esi
40086a: 48 89 df mov %rbx,%rdi
40086d: e8 a2 00 00 00 callq 400914 <_ZN6PersonC1Ei>
400872: 48 89 5d e8 mov %rbx,-0x18(%rbp)
400876: 48 8b 45 e8 mov -0x18(%rbp),%rax
40087a: 48 89 c7 mov %rax,%rdi
40087d: e8 da 00 00 00 callq 40095c <_ZN6Person5speakEv>
400882: 48 8b 5d e8 mov -0x18(%rbp),%rbx
400886: 48 85 db test %rbx,%rbx
400889: 74 10 je 40089b <main+0x4e>
40088b: 48 89 df mov %rbx,%rdi
40088e: e8 b1 00 00 00 callq 400944 <_ZN6PersonD1Ev>
400893: 48 89 df mov %rbx,%rdi
400896: e8 45 fe ff ff callq 4006e0 <_ZdlPv@plt>
40089b: b8 00 00 00 00 mov $0x0,%eax
4008a0: eb 16 jmp 4008b8 <main+0x6b>
4008a2: 49 89 c4 mov %rax,%r12
4008a5: 48 89 df mov %rbx,%rdi
4008a8: e8 33 fe ff ff callq 4006e0 <_ZdlPv@plt>
4008ad: 4c 89 e0 mov %r12,%rax
4008b0: 48 89 c7 mov %rax,%rdi
4008b3: e8 98 fe ff ff callq 400750 <_Unwind_Resume@plt>
4008b8: 48 83 c4 10 add $0x10,%rsp
4008bc: 5b pop %rbx
4008bd: 41 5c pop %r12
4008bf: 5d pop %rbp
4008c0: c3 retq
0000000000400914 <_ZN6PersonC1Ei>:
400914: 55 push %rbp
400915: 48 89 e5 mov %rsp,%rbp
400918: 48 83 ec 10 sub $0x10,%rsp
40091c: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400920: 89 75 f4 mov %esi,-0xc(%rbp)
400923: 48 8b 45 f8 mov -0x8(%rbp),%rax
400927: 48 c7 00 21 0a 40 00 movq $0x400a21,(%rax)
40092e: 48 8b 45 f8 mov -0x8(%rbp),%rax
400932: 8b 55 f4 mov -0xc(%rbp),%edx
400935: 89 50 08 mov %edx,0x8(%rax)
400938: bf 28 0a 40 00 mov $0x400a28,%edi
40093d: e8 8e fd ff ff callq 4006d0 <puts@plt>
400942: c9 leaveq
400943: c3 retq
0000000000400944 <_ZN6PersonD1Ev>:
400944: 55 push %rbp
400945: 48 89 e5 mov %rsp,%rbp
400948: 48 83 ec 10 sub $0x10,%rsp
40094c: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400950: bf 3c 0a 40 00 mov $0x400a3c,%edi
400955: e8 76 fd ff ff callq 4006d0 <puts@plt>
40095a: c9 leaveq
40095b: c3 retq
000000000040095c <_ZN6Person5speakEv>:
40095c: 55 push %rbp
40095d: 48 89 e5 mov %rsp,%rbp
400960: 48 83 ec 10 sub $0x10,%rsp
400964: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400968: 48 8b 45 f8 mov -0x8(%rbp),%rax
40096c: 8b 50 08 mov 0x8(%rax),%edx
40096f: 48 8b 45 f8 mov -0x8(%rbp),%rax
400973: 48 8b 00 mov (%rax),%rax
400976: 48 89 c6 mov %rax,%rsi
400979: bf 50 0a 40 00 mov $0x400a50,%edi
40097e: b8 00 00 00 00 mov $0x0,%eax
400983: e8 28 fd ff ff callq 4006b0 <printf@plt>
400988: c9 leaveq
400989: c3 retq
40098a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
- main主函数汇编分析
main汇编分析如下,主要要以下5个过程
- 第一步:new,分配内存——_Znwm@plt
- 第二步:new,Person类的构造函数——_ZN6PersonC1Ei
- 第三步:调用Person->speak函数——_ZN6Person5speakEv
- 第四步:调用Person类的析构函数——_ZN6PersonD1Ev
- 第五步:释放new分配的内存——ZdlPv@plt
- Person的构造函数——_ZN6PersonC1Ei
从Person的构造函数我们可以知道,Person类的初始化只有和成员变量初始化相关的,和成员函数无关。
- 结论:类的大小只和成员变量的个数和成员变量的大小有关。和成员函数无关。
0000000000400914 <_ZN6PersonC1Ei>:
400914: 55 push %rbp
400915: 48 89 e5 mov %rsp,%rbp
400918: 48 83 ec 10 sub $0x10,%rsp
40091c: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400920: 89 75 f4 mov %esi,-0xc(%rbp)
400923: 48 8b 45 f8 mov -0x8(%rbp),%rax
400927: 48 c7 00 21 0a 40 00 movq $0x400a21,(%rax)
40092e: 48 8b 45 f8 mov -0x8(%rbp),%rax
400932: 8b 55 f4 mov -0xc(%rbp),%edx
400935: 89 50 08 mov %edx,0x8(%rax)
400938: bf 28 0a 40 00 mov $0x400a28,%edi
40093d: e8 8e fd ff ff callq 4006d0 <puts@plt>
400942: c9 leaveq
400943: c3 retq
- Person的成员函数——_ZN6Person5speakEv
当我们调用类的成员函数时
jun->speak();
其实是去找到类的成员函数地址去执行了,speak函数地址是
000000000040095c <_ZN6Person5speakEv>:
将隐式传递的第一个参数(即this指针)存储到堆栈上的-0x8(%rbp)处。由于this指针存储了对象的地址,因此在接下来的指令中,可以通过this指针来访问对象的成员变量。
400968: 48 8b 45 f8 mov -0x8(%rbp),%rax
使用mov指令将-0x8(%rbp)中存储的this指针加载到%rax寄存器中,用于后续指令对对象的操作。
40096c: 8b 50 08 mov 0x8(%rax),%edx
使用mov指令和偏移量0x8,从%rax寄存器指向的对象的地址中获取m_name成员变量的地址,并将其加载到%edx寄存器中。注意,由于m_name是一个字符串类型的成员变量,因此其地址也是字符串的地址,可以直接用于后续printf函数的调用。
40096f: 48 8b 45 f8 mov -0x8(%rbp),%rax
再次使用mov指令将-0x8(%rbp)中存储的this指针加载到%rax寄存器中,用于后续指令对对象的操作。
400973: 48 8b 00 mov (%rax),%rax
通过%rax寄存器访问对象的虚函数表指针(vptr),并将其加载到%rax寄存器中。
400976: 48 89 c6 mov %rax,%rsi
使用mov指令将%rax寄存器中的值(即指向虚函数表的指针)复制到%rsi寄存器中,为后续printf函数的调用做准备。
400979: bf 50 0a 40 00 mov $0x400a50,%edi
三、new的源码
1、windows
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\crt\src\vcruntime文章来源:https://www.toymoban.com/news/detail-544967.html
//
// new_scalar.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Defines the scalar operator new.
//
#include <stdlib.h>
#include <vcruntime_new.h>
#include <vcstartup_internal.h>
// Enable the compiler to elide null checks during LTCG
#pragma comment(linker, "/ThrowingNew")
// new() Fallback Ordering
//
// +----------+
// |new_scalar<---------------+
// +----^-----+ |
// | |
// +----+-------------+ +----+----+
// |new_scalar_nothrow| |new_array|
// +------------------+ +----^----+
// |
// +------------+----+
// |new_array_nothrow|
// +-----------------+
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}
参考
1、《程序员的自我修养链接装载与库》文章来源地址https://www.toymoban.com/news/detail-544967.html
到了这里,关于【编译、链接、装载十六】汇编与指针、汇编与new的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!