一、深入学习c++先要练好的内功
掌握进程虚拟地址空间区域的划分
课程讲的内容建立在x86 32位的Linux系统下。
任何的编程语言会产生两种东西:指令和数据。磁盘上的可执行文件在启动时都会加载到内存当中,但是不会加载到物理内存中,是放在进程的虚拟地址空间中。

在4g内存中,有3g内存是用户空间,1g内存是内核空间。用户空间分为代码段.text,只读数据段.rodata,数据段.data(存放初始化后的数据),.bss(存放未初始化的数据,操作系统会默认赋值为0),堆,加载共享库,栈,最后是命令行参数和环境变量。内核空间分为三块。
每个进程的用户空间是私有的,但是内核空间是共享的,就有这样一个问题:进程之间的通信方式有哪些?匿名管道通信=》在将通信的内容放在内核空间中。
面试中常问的问题:指令在运行的时候放在内存的哪个区域?代码段.text
从指令角度掌握函数调用堆栈的详细过程
两个问题:
#include<iostream>
using namespace std;
/*
问题1:main函数调用sum,sum执行完后,怎么知道回到哪个函数
问题2:sum执行完,回到main函数之后怎么知道从哪一行继续执行
*/
int sum(int a, int b) {
int temp = 0;
temp = a + b;
return temp;
}
int main() {
int a = 10;
int b = 20;
int ret = sum(10, 20);
cout << "ret:" << ret << endl;
return 1;
}
代码在内存中是怎么存在的。在代码运行时会在内存中生成相应的栈帧,保存代码所需的内存。esp为栈顶地址,ebp为栈底地址(为高位地址)。

从main函数开始,代码在内存保存数据,首先是两个赋值语句,被放在栈底的上面,汇编代码为 mov dword ptr[ebp-4], 0Ah。运行到ret的时候,首先将数据存在栈中,然后调用函数,函数会首先push到栈顶,函数的两个参数被从右到左push到栈顶,同时esp会向上移动指向栈顶。然后将函数所在的地址压入栈内,再将ebp压入栈中,为sum函数开辟新的栈帧。

栈帧内的元素默认为0xCCCCCCCC。依旧是按照函数内的指令在内存中存数据。遇到}后会将上方的内存进行出栈,ebp指向原来main函数的地址,esp回到栈顶,实参上面的内容全都不要了。这个时候如果访问到esp上面的地址会报错,但是esp上面的地址中的数据仍然是之前的数据没有删除。
举个例子:
int* func(){
int data=10;
return &data;
}//这个内存是不安全的,保存在栈顶开辟的内存中,虽然函数调用之后不会马上消失,但是如果调用新函数这个内存中的数据就会随着新函数改变
int* p=func();//如果后面调用新函数,这个操作就会失效
cout<<*p<<endl;
从编译器角度理解c++代码的编译和链接原理
首先明确的是c++代码在生成为可执行文件的过程中要经过编译和链接两个过程。
-
一、编译过程
- 预编译
- 编译
- 汇编
编译过程结束后会生成一个二进制可重定位的目标文件(*.obj)
.obj文件的格式组成是什么? 是由elf文件头和上节中讲到的.data .bss等段组成。
此时在该文件中还没有分配虚拟地址,但是在指令中地址被设置为000000。同时在该文件中声明的代码被放置在UND段表示没有定义。
-
二、链接过程(将编译完成所有.obj文件和静态库文件链接成可执行文件)
- 将所有.obj文件段合并,符号表合并,进行符号解析 (对.data .bss等各个段进行合并)
- 符号的重定位 符号解析成功以后=》给所有符号分配虚拟地址
生成可执行文件。执行的时候在cpu对虚拟地址进行映射,映射到物理地址中
符号解析:对所有符号的引用,都要找到该符号定义的地方。就是找到定义UND段的地方。
.exe/.out文件的格式组成是什么?:与.obj文件相比,多了一个program header。该header段中有两个load=》告诉系统运行这两个程序的时候把哪些内容加载到内存当中。
extern int gdata;//这句话是声明,告诉编译器去别的文件中找gdata的定义。

上图是程序在磁盘中的存在形式和执行的过程。

浙公网安备 33010602011771号