通过汇编深入理解C++语言

最近整理印象笔记的笔记,找到以前在深信服做病毒逆向分析时的笔记,总结分享下,算是比较好的入门汇编的材料,强烈建议想掌握C和C++本质的同学,动手写些简单的例子代码,再Debug模式下(注意不要用Release模式,因为很多细节会被优化掉),对照源码看汇编代码,能让你对C和C++有更本质的认识,另外建议先看懂CSAPP中的汇编入门章节,再来看我的博客比较好。

变量

  • 局部变量:通过减小或增大栈指针来分配或回收变量内存。

  • 宏和typedef:编译阶段替换成变量的实际数据或函数的汇编代码。

  • 全局变量:在main函数前被初始化,被写入.data节,由绝对地址寻址。

  • 静态变量:仅能被初始化一次,汇编中可看到防止重复初始化的标记位。若初始化时是常量则会被优化。

  • const变量:汇编层次和普通变量没区别,仅在编译阶段防止被改
    下图一张图揭示不同类型变量的汇编代码的本质
    通过汇编深入理解C++语言_第1张图片

复合变量

数组

看汇编代码可以发现,数组和普通的变量都是栈上分配的内存,只是数组是连续的。若数组太大,会导致栈溢出。数组中的中括号会编译成(数组首地址+索引*偏移量)计算具体地址。所以数组和指针本质是相同的,都是操作地址。类似的多维数组和一唯数组也没区别。
通过汇编深入理解C++语言_第2张图片

数组指针和普通指针没区别,且数组名就是数组的首地址
通过汇编深入理解C++语言_第3张图片

结构体

可把结构体看作特殊数组,成员变量再内存中也是连续的,但偏移量不同,由偏移量确定结构体的成员。因此,结构体类型只是提升代码可读性,汇编代码和通过"首地址+偏移量"确定变量本质是一样的
通过汇编深入理解C++语言_第4张图片
通过汇编深入理解C++语言_第5张图片

枚举变量

枚举变量都被实际值替换,类似宏定义
通过汇编深入理解C++语言_第6张图片

共用体变量

共用体中的成员都共享同一内存空间,所以后面的成员会把前面的成员覆盖掉,导致再次访问时会出现奇怪的数据,所以一般很少用共用体变量
通过汇编深入理解C++语言_第7张图片

程序结构

if-else结构

判断ZF标志位确定cmp、jxx等条件分支的跳转方向。
通过汇编深入理解C++语言_第8张图片

三目运算结构

和if-else本质都一样,通过ZF标志进行跳转
通过汇编深入理解C++语言_第9张图片

循环结构

  • while:一般进行两次跳转,也有三次跳转的,效率比do-while效率低
    通过汇编深入理解C++语言_第10张图片

  • do-while:和while基本没区别,但是只有1次跳转。效率较高
    通过汇编深入理解C++语言_第11张图片

  • for循环:效率是循环中最慢的,在Release版被有化成do-while结构
    通过汇编深入理解C++语言_第12张图片

可看到化成了do-while通过汇编深入理解C++语言_第13张图片

函数和指针函数

  • 指针函数:本质是将函数首地址赋值给另一个变量通过汇编深入理解C++语言_第14张图片如果函数指针作为某个函数的参数,则该函数叫做回调函数

通过汇编深入理解C++语言_第15张图片

面向对象分析

C++语言本身再编译器层面做了很多底层的工作,如果对C++的理解只限于语言层面,我相信很难成为优秀的C++开发者,因此,面向对象分析是本博客的重点,需要认真读,最好是写写简单的代码,对照我的博客看汇编和源代码,对深入理解C++很有帮助!

构造函数

每个对象都有个指向该对象的this指针,对象通过this指针调用成员或函数。注意构造函数是有返回值的,返回的是构造的对象的指针,用eax寄存器存储。注意this指针和指向对象的指针不同!
通过汇编深入理解C++语言_第16张图片

查看构造函数,可看到先获取this指针,然后初始化this指针指向的对象(这里只有一个对象成员m_pBuff,故地址也就是this指针)为0,最后返回该指向该对象的指针(也是this指针,想想为什么?)
通过汇编深入理解C++语言_第17张图片

成员变量

通过汇编深入理解C++语言_第18张图片

观察内存布局,先定义的类成员会放在低地址,后定义的成员放高地址。
this指针:ecx中保存的值,但并不是一定就用ecx保存this指针,但大部分情况下是这样的。
类成员:ecx保存的地址加上对应偏移量的地址,指向的数据。
有参构造函数和无参相比只是多了参数传递, 都是在调用构造前,将this指针放入ecx中

析构函数

对象调用相同的析构函数,但传递的this指针不同,故能正确释放对象。
在这里插入图片描述

全局对象

在main函数之前定义,在程序加载前就存在。其构造和析构与普通构造、析构略有不同。

构造函数

通过栈图可看到,全局对象都在_cinit()中预先初始化,然后调用_inititerm并通过二级指针遍历函数指针,找到全局对象的构造函数并调用
通过汇编深入理解C++语言_第19张图片

通过汇编深入理解C++语言_第20张图片

析构函数

查看栈试图,可以看到调用流程。
通过汇编深入理解C++语言_第21张图片
doexit中通过函数指针遍历循环,通过委托函数调用全局函数的析构函数。
通过汇编深入理解C++语言_第22张图片
通过汇编深入理解C++语言_第23张图片

成员函数

也是将this指针通过ecx传递,通过this指针寻找成员变量。如图
通过汇编深入理解C++语言_第24张图片

拷贝构造函数

拷贝构造函数比普通构造函数多个将g_MyString地址作为参数的过程
在这里插入图片描述

拷贝构造函数将要复制的类的地址作为参数,被拷贝的对象和新对象是不同对象,只不过再拷贝过程中,使用引用获取拷贝内容,不要把拷贝函数和对象引用搞混。
在这里插入图片描述

对象指针

先调用new申请堆空间(汇编代码中有检测是否new成功)再调用构造
通过汇编深入理解C++语言_第25张图片
析构刚好相反先析构对象(对象指针的析构会有个中间代理,由代理决定是否调用析构函数),再释放堆空间
通过汇编深入理解C++语言_第26张图片

通过汇编深入理解C++语言_第27张图片

继承

子类构造函数

子类的构造函数中会将this指针传给父类,并使用该this指针调用父类的成员或函数。
且父类成员变量在低地址,子类成员变量在高地址,根据定义顺序依次排列。
通过汇编深入理解C++语言_第28张图片

子类析构函数

在子类析构函数中,和构造函数一样利用子类的this指针调用父类的析构函数。
通过汇编深入理解C++语言_第29张图片

虚函数

有虚函数的类的构造函数中会初始化一个虚函数表,这个虚函数表有对应的虚函数地址。再有了虚函数情况下,会在虚表未初始化情况下调用父类构造函数
通过汇编深入理解C++语言_第30张图片

不能在构造、析构中调用虚函数,不起作用(子类的构造函数未将虚函数表初始化就调用父类构造函数,故调用父类构造后this指针指向的是子类对象,所以调用函数还是普通调用没有经过虚函数表查询;析构类似)
类中有虚函数的类,会提供默认的构造函数初始化虚函数表指针,否则就没有默认构造函数

多继承

一个类同时继承多个类,具有继承类中所有类成员、类方法。按照继承顺序将父类的虚函数表、成员变量及自身虚函数表、成员变量在内存中按顺序初始化。如图所示:
通过汇编深入理解C++语言_第31张图片

取对象地址给到父类指针中,取出虚表指针,调用对应的虚函数,或成员变量(调整偏移)
通过汇编深入理解C++语言_第32张图片

对象的强制类型转换实际是通过拷贝构造函数实现(最终还要析构掉生成的对象 ),从而实现调用父类成员变量。

析构是谁先被构造谁就最后被析构
构造时对this指针做加法,有类成员就跳过类成员加虚表长度,析构时,对this指针做减法,就是多重继承最大的特点,只有多重继承中,才会使用this指针做减法

虚继承类(菱形结构)

将祖父类带上virtual,表示告诉编译器,如果祖父类没有被构造。则构造一份祖父类的拷贝,否则使用已经构造的那个。再汇编代码中通过标记来判断是否已经被构造
再菱形结构中,内存分布顺序是父类、自己、祖父类

总结

先简单分析C语言的变量、复合变量、程序结构,接着再深入分析面向对象,包括构造函数、析构函数、全局对象、成员函数、拷贝构造函数、对象指针、继承、虚函数、多继承、虚继承。相信看懂博客并动手实践的朋友,会对C++有较深的认识和理解。

你可能感兴趣的:(深入理解计算机系统,汇编,c++)