C++基础(一)——底层分析

本文详细介绍了进程的虚拟地址空间,包括用户空间和内核空间,以及数据在不同段的存储。讨论了进程间通信的匿名管道方式。接着阐述了函数调用时的堆栈操作,如call指令和ret指令的作用。最后,概述了C++的编译和链接过程,涉及预编译、编译、汇编和链接阶段,以及生成的可执行文件格式。
摘要由CSDN通过智能技术生成

01 进程的虚拟地址空间

一、操作系统让进程访问的是虚拟地址空间,而不是物理地址

1.任何程序在编译时都会产生指令和数据,进行地址编号,但是如果地址不连续,就会程序运行不起来,编译器的地址管理比较麻烦(无法动态的获知物理空间的使用情况,也就无法为数据进行编号)

2.进程直接访问物理地址,如果此时有一个野指针,那么在进行操作野指针的时候可能会改变其他空间的数据,造成不安全的事件发生(无法进行内存访问控制)

3.程序运行空间通常需要一块连续的空间,空间利用率低,通过虚拟地址空间映射到物理内存上进行数据存储,可以实现数据在物理空间上离散式存储,提高内存的利用率,并且每个进程都有一份属于自己的连续空间使用

二、进程的虚拟地址空间

image-20220325135414636

指令存放在.text段

int a = 12;
int b = 0;
int c;
// 编译后成为汇编指令 
// mov dword ptr[a],0ch
//存放.text段
//局部变量 指令

数据存放在

static int e = 13;
static int f = 0;
static int g;
//局部变量静态变量e存放在.data段 不为0 f、g存放在.bss段 为0/未初始化

可打印g的值为0,无法打印c的值(栈上的无效值)

#include <iostream>
using namespace std;

int main(){
    int c;
    static int g = 0;
    cout << c << g << endl;
    
    return 0;
}
//无法打印c的值 可以打印g的值为0

1.包括了用户空间、内核空间

2.全局变量均存放在data段(不为0)、bss段(为0 或者 未初始化)

3.局部变量 产生指令

局部变量中的静态变量 产生数据(data段、bss段)

三、进程和线程的区别

进程是系统进行资源分配和调度的一个独立单位

线程是进程的一个实体,是CPU调度和分配基本单位

简而言之

一个程序至少要有一个进程,一个进程至少要有一个线程

四、多进程

1.有各自的用户空间(隔离),共享内核空间

2.进程之间通信方式 : 匿名管道通信(内核空间内存数据可共享)

参考链接

1.https://blog.csdn.net/weixin_45975835/article/details/115010310

2.《深入理解计算机系统》 第七章

3.https://blog.csdn.net/yaosiming2011/article/details/44280797

02 函数调用堆栈

#include <iostream>
using namespace std;

int sum(int a, int b) {
    int temp;
    temp = a + b;
    return temp;
}
/*
push ebp
mov ebp,esp//sum函数栈帧
sub esp,4Ch//给esp开辟栈帧空间

mov dword ptr[ebp-4]//int temp = 0;
mov eax,dword ptr[ebp+0Ch]
add eax,dword ptr[ebp+8]
mov dword ptr[ebp-4],eax

mov eax,dword ptr[ebp-4]//return temp
mov esp,ebp
pop ebp
ret
*/

int main() {
    int a = 10;//局部变量不产生符号 mov dword ptr[ebp-4], 0Ah
    int b = 20;// mov dword ptr[ebp-8], 14h
    int ret = sum(a,b);
    /*
    mov eax,dword ptr[ebp-8] //把b传给sum的形参int b
    push eax //压栈
    mov eax,dword ptr[ebp-4]
    push eax
    call sum
    
    add esp,8
    mov dword ptr[ptr-0ch],eax
    */
    cout << "ret is " << ret << endl;

    system("pause");
    return 0;
}

Q1:main函数调用sum,sum执行完以后,怎么知道回到哪个函数中?

通过汇编语言中的call指令存储了下一条指令的地址。

image-20220429103602839

image-20220429110141856

Q2:sum函数执行完,回到main以后,怎么知道从哪一行指令继续运行的?

在sum函数调用结束以后,ret指令在CPU的PC寄存器中读取刚刚存储的地址,回到原来的函数。

03 C++的编译与链接

编译过程

main.cpp sum.cpp

extern int gdata;// gdata *UND*  符号引用
int sum(int, int);// sum *UND*

int data = 20; //data .data段

int main() { //main .text段
    int a = gdata;
    int b = data;
    
    int ret = sum(a, b);
    
    return 0;
}
int gdata = 10;//gdata .data段

int sum(int a, int b) { //sum_int_int  .text段
    return a + b;
}

预编译:

’#‘开头的命令在预编译完成

#pragma lib
#pragma link
//在链接阶段处理

编译:

gcc
g++ -o

汇编:

主要是符号表的输出

二进制可重定位的目标文件(*.obj) main.o sum.o

其中,.o文件的格式组成:

elf文件头

.text

.data

.bss

.symbal

.section table

链接过程

链接:编译完成的所有.o文件和静态库文件

image-20220429131539394

步骤一:

所有.o文件段的合并==(.text、.bss、.data等)==

符号表合并后,进行符号解析(UND所有对符号的引用,都要找到该符号的定义的地方,可能会报错,符号未定义、重定义等)

给所有的符号分配虚拟地址(在此时给符号分配虚拟地址)

步骤二:

符号的重定位(重定向

在代码段将符号的地址填成正确的地址

查询符号段、地址指令
objdump -s sum.o
objdump -t a.out
readelf -h a.out

输出

a.out

读取a.out的入口地址,入口地址为main函数的地址

都是各种段组成,.out相比于.obj多了progrma headers段,有两个load,告诉系统运行到这个程序的时候,把哪些内容加载到内存中(.text、.data)

-o 可执行程序的名称

a.exe

可执行文件加载过程:

image-20220429140042960

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-特立独行的猪-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值