文章目录
- C++从源代码到可执行程序经过什么步骤
- 静态链接和动态链接
- 类的对象存储空间
- C++的内存分区
- 内存池
- 在成员函数中调用delete this会出现什么问题?
- 如果在类的析构函数中调用delete this,会发生什么?
C++从源代码到可执行程序经过什么步骤
- 预处理:预处理主要是对伪指令和特殊符号进行处理,将.c文件转为.i文件,具体处理如下:
-
- 宏定义:如#define Name TockenString等,预编译所要作的是将程序中的全部Name全部用TockenString替换
- 处理所有的条件编译指令,如#if、#endif、#ifdef等
- 处理#include预编译指令,将文件内容替换到它的位置,该过程是递归进行的
- 删除所有的注释//和/**/
- 保留所有的#pragma编译器指令,编译器需要使用它们
- 添加行号和文件标识,便于编译时编译器产生调试用的行号信息,该行号也是编译时产生编译错误或警告能够显示行号
- 编译:经过预处理后的代码进行一系列词法分析、语法分析、语义分析及优化被编译器转换为汇编语言代码。
- 汇编:将汇编代码转变成机器可以执行的指令(机器码文件)
- 链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。链接分为静态链接和动态链接
静态链接和动态链接
-
静态链接和动态链接两者最大的区别在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时
-
静态链接:
静态链接是在编译时将程序所需的库文件的代码复制并链接到最终的可执行文件中。在静态链接时,所有用到的库函数的代码都被复制到可执行文件中,使得可执行文件完全独立,不再依赖外部的库文件。这意味着在运行时不需要加载额外的库文件,因此可执行文件的大小较大。
静态链接的步骤包括编译源代码文件、将对象文件打包成静态库文件(通常以.lib或.a为扩展名),然后将静态库文件链接到最终的可执行文件中。可执行文件中包含了所有用到的库函数的代码,因此可以独立地在任何支持相应系统架构的计算机上运行。
-
静态链接优缺点:
优点:代码装载速度快,执行的时候运行速度快
缺点:一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以可能同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
-
动态链接:
动态链接是在运行时将程序所需的库文件的代码与程序代码进行动态链接,形成最终的可执行文件。
动态链接的步骤包括编译源代码文件、将对象文件打包成共享库文件(通常以.dll或.so为扩展名),然后在运行时动态地加载共享库文件,并将程序与共享库连接在一起。
-
动态链接优缺点:
优点:节省内存;适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试;更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍;把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
类的对象存储空间
- 非静态成员的数据类型大小之和
- 编译器加入的额外成员变量(如虚表指针)
C++的内存分区
-
内存可以整体分为三个部分:栈区、堆区、静态常量区。栈区主要存储的是局部变量和函数参数;C语言当中malloc、calloc、realloc、C++当中的new申请的空间基本是在堆区,堆区也被称为自由存储区;静态常量区有两个主要的标记:static定义的变量和字符串常量。静态常量区细分可以是数据段和代码段,全局变量和static变量存放在数据段,字符串常量这种只读常量放在代码段。
-
栈又叫堆栈,存储非静态局部变量、函数参数、返回值等等,栈是向下增长的。
-
内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
-
堆用于程序运行时动态内存分配,堆是可以向上增长的。
-
数据段存储全局数据和静态数据。
-
代码段存储可执行的代码和只读常量。
内存池
- 内存池是池化技术的应用之一,池化技术就是程序先向系统申请过量的资源,资源由程序自己来管理,以备不时之需。之所以一次提前申请过量的资源,是因为每次申请该资源都有较大的开销,不如提前申请放着程序自己管理,这样使用的时候就非常方便,大大提高程序的运行效率
- 内存池是指程序预先从操作系统申请一块足够大的内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。
- 内存池通常是由一块连续的内存区域组成,被划分为大小相等的块或者按照一定的规则进行划分。当需要分配内存时,从内存池中取出一个合适大小的块,当内存不再使用时,将其放回内存池以供后续的再利用。内存池管理者会维护一个空闲块链表,用于记录可用的内存块,这样在分配内存时可以快速找到合适大小的块。
- 内存池主要解决的问题:提高内存分配的效率,减少了频繁的内存分配和释放操作;内存碎片问题,内存池通过固定大小的块进行内存分配,避免了因为不同大小的内存块交替使用而导致的内存碎片问题;提高程序性能:通过减少内存分配和释放的次数,内存池可以显著提高程序的性能和响应速度。
在成员函数中调用delete this会出现什么问题?
- delete this 相当于“类对象的自杀”,delete之后,类对象被释放掉,但是只要不涉及到this的指针,任何函数调用都还可以正常进行,为什么呢?
- 根本原因在于内存模型,在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含类的成员函数,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,告诉成员函数是哪个对象在调用它。
- 虽然delete之后,类对象被释放掉,在成员函数中调用delete this会出现什么问题?为什么不是指针错误,或者无访问权限令系统崩溃的问题呢?因为释放掉的内存空间并不是立刻被回收的,所以有可能还是可以访问的,只是值是不确定的。当访问或操作数据成员的时候,可能得到一串未初始化的随机数,访问虚函数表,就会出现不可预期的问题。
- 为什么是不可预期的问题?
- 上面解释了内存空间并不是马上被回收到系统中,此时这段内存还是可以访问的,但是其中的值却是不确定的,当你获取数据成员,可能得到的是一串很长的未初始化的随机数;访问虚函数表,指针无效的可能性非常高,造成系统崩溃。
如果在类的析构函数中调用delete this,会发生什么?
- 会导致堆栈溢出。因为delete的本质是“为将被释放的内存调用析构函数,然后,释放内存”。delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃.