C++ 编译与内存


本章内容概述

本文意在记录笔者在学习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,一个追求不断进步的学生,期待你的关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alkaid3529

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值