文章目录
本章内容概述
本文意在记录笔者在学习C++面试相关知识时的笔记记录,提高C++面试水平,扩充C++知识面,对书中的知识点进行记录、自我分析与总结。
一、编译与链接
1.编译
编译,是将源程序中的C语言,翻译为CPU可以理解的,机器可以执行的二进制指令,因此,编译的过程,本质上也就是翻译的过程,过程中涉及很多复杂的步骤。
编译器读取源文件.cpp,将其翻译为可执行文件ELF,经过操作系统加载即可执行。在整个编译器的操作过程中,可以分为四个步骤:
编译预处理
,在预处理阶段,主要对源代码中的预处理指令进行处理,如加入头文件,去除注释,条件编译,宏替换,添加行号,保留编译器指令等。
编译
,在编译阶段,针对预处理后的文件进行语法分析、语义分析,生成汇编代码,即将.cpp文件翻译为.s汇编文件。
汇编
,在汇编阶段,编译器将.s汇编文件翻译为.o机器指令文件,需要注意,一个.cpp文件最终只会得到一个.o文件。
链接
,在链接阶段,需要将汇编生成的多个.o目标文件链接成为一个整体,从而生成可执行的ELF文件。因为在一些情况下,一个.cpp文件生成的.o文件无法单独执行,可能调用了其他文件或库文件中的函数,因此需要链接处理。
2.链接
链接
,分为两种:静态链接和动态链接。
2.1静态链接
静态链接是指在程序汇编阶段,就已经将程序需要的全部外部调用函数拷贝到可执行文件中,在程序被执行时,全部代码也会载入内存。
2.2动态链接
动态链接是指,在生成可执行文件时,将程序调用的部分程序放到动态链接库中,链接程序只记录共享对象部分信息,在程序执行过程中,如果需要调用,才会加载至内存。
可以直观感受到,静态链接对空间的需求会更大一些,因为它将全部调用到的外部函数都拷贝到了可执行文件当中,但是速度会更快一些,省去了加载动态库的时间;动态链接会节省空间,只有在需要时才会加载,但是也会导致速度变慢。
二、内存管理
1.ELF文件
可链接与可执行格式(Executable and Linkable Format),是用于可执行文件、目标代码、共享库和核心转储的标准文件格式,ELF文件构成如图:
可以看到,可执行程序内部是分段存储的。
.text section
:代码段,存放已编译程序的代码,OS加载后此部分只读。
.rodata section
:只读数据段,存放程序中会使用的常量,如字符串,不可修改。
.data section
:数据段,存放初始化的全局变量、常量。
.bss section
:bss段,存放未初始化全局变量,仅做占位符。
ELF被操作系统加载至内存时,会依次读取各段内容,同时分配空间,启动进程。
2.内存分区
C++使用内存分区通常包括:栈区、堆区、全局/静态存储区、常量区、代码区。
栈区
:存放函数调用的局部变量、函数参数、返回地址等信息,由系统默认分配,在进程结束后,栈区空间才会被系统回收。
堆区
:动态申请的内存空间,需要手动申请和释放,否则会在进程结束后由系统回收。
全局/静态存储区
:主要为.bss和.data段,存放全局变量和静态变量,在C中,初始化的在.data段,未初始化的在.bss段,在C++中不再区分。
常量存储区
:主要为.rodata段,存放常量,不允许修改,程序运行结束自动释放。
栈区
:主要为.text段,存放代码,可执行但不可修改,存放编译后的二进制文件。
三、变量定义与生存周期
从作用域和生命周期两个维度,可以描述一个变量在实践和空间中的属性。
1.作用域
常见作用域可分为:全局作用域,局部作用域,语句作用域,类作用域,文件作用域,命名空间作用域。
在各种类型的变量中,全局变量具有全局作用域,配合extern关键字可作用于所有源文件;静态全局变量具有文件作用域,作用在定义变量的整个文件中,并且只有一份;不能作用于其他文件;局部变量具有局部作用域,只在函数运行期间存在,对外不可见;静态局部变量具有局部作用域,只被初始化一次,仅函数内部可访问。
2.生命周期
生命周期,即变量可使用的时间段。
全局变量,在整个程序运行期间一直存在,进程结束后被销毁,系统回收;局部变量只存在函数被调用期间,函数返回后,变量被销毁;静态局部变量,虽然只作用在函数内部,但在整个程序运行期间一直存在,与全局变量相同。
四、内存对齐
内存对齐,可以提高CPU访问内存的效率,各种类型数据都存放在机器字的整数倍的地址指向的内存中。否则,在不对齐的情况下,会是的CPU访问内存的次数增加,可以通过代码观察内存对齐现象:
class test
{
int a;
char c;
};
cout << sizeof(test) << endl; //8
虽然char型变量大小为1,int型变量大小4,但基于内存对齐的原则,最终test的大小为8。这样的处理方式一方面提高了CPU访问内存效率,一方面也使得程序可以在不支持随意访问内存的系统下运行。
五、大端与小端
字节顺序,又称端序或尾序,指多字节对象在内存中存储字节的排列方式,又从高到低和从低到高两种。
1.Little-Endian
将低序字节存储在起始地址,在变量指针转换或数据截断时,地址保持不变,对计算机更自然。
2.Big-Endian
将高序字节存储在起始地址,内存顺序和数字的书写顺序一致,更符合人的直观认识。网络字节规定统一采用大端。
六、内存泄漏检测与预防
1.内存泄漏
在编写程序时,在堆区动态开辟的内存未被手动释放,就会导致堆中可使用的内存逐渐减少,最终可能导致因内存不足产生的系统崩溃。
内存泄漏不是指内存真的消失,而是因错误或疏忽失去了对某处内存的控制,产生浪费;内存泄漏大多在堆区,因为栈区的变量会在生命周期结束后自动释放。
堆区的内存管理需要格外注意,对于每次申请的到的内存,都应当妥善处置,在目前的编译器中,一般都会在程序结束后由编译器强制释放。
2.内存泄露检测工具
valgrind
是一个功能强大的内存检测工具,运行在linux平台下,可以直观感受程序运行期间的内存分配,代码如下:
在注释掉手动释放堆区内存的代码后,使用valgrind观察内存状况:
可以看到,运行显示存在一处未释放的堆区内存。
3.内存泄漏预防
对于在堆区开辟空间的类,在析构函数中必须进行释放,对于每一个"new",都必须有对其负责的"delete"。或者使用系统提供的可靠的智能指针,同时利用工具检测内存。
七、智能指针
智能指针部分的内容较为繁琐,后续会推出专门的讲解。
本章总结
本章分析了 C++ 程序的编译过程,以及可执行文件的内存分布,程序运行期间变量的分布,和 Linux 下检查内存泄露的一些工具,希望对面试有所帮助。
最后,我是Alkaid#3529,一个追求不断进步的学生,期待你的关注!