总所周知,任何的编程语言都会产生指令和数据。例如下面一段代码
#include<iostream>
#include<string.h>
using namespace std;
int gdata1 = 10;
int gdata2 = 0;
int gdata3;
static int gdata4 = 11;
static int gdata5 = 0;
static int gdata6;
int main()
{
int a = 12;
int b = 0;
int c;
static int e = 13;
static int f = 0;
static int g;
return 0;
}
这段代码经过编译产生**.exe的可执行文件是在磁盘上**的,通过cup将其加载到内存中。
但是注意!不可能直接加载到物理内存上。系统都会给当前进程开辟一个虚拟地址空间。接下来,我们来好好了解一下进程虚拟地址空间吧~
1、进程虚拟地址空间大小
在X86 32位linux系统中,linux系统会给当前进程分配一个2^32大小的一块空间(4G)
2、虚拟的含义
关于虚拟的含义,我们可以这样来解释。
它存在,你能看得见,他是物理的。
它存在,你看不见,他是透明的。
他不存在,你却看得见,他是虚拟的。
他不存在,你也看不见,他被删除了
3、空间分配情况
如下图所示:
总体上来说,进程虚拟地址空间分为用户空间和内核空间两个部分,从上到下地址减小。接下来,我们来介绍相关区域的具体含义。
(1)不可访问区
由字面意思我们就可以了解到,它是指的这段区域是我们不可访问的,如下面一段代码:
char* p = nullptr;
strlen(p);
char* src = nullptr;
strcpy(dest,src)
p指向了一个不可访问区,我们从0地址打算拷贝一个字符串,但是程序运行直接崩溃,因为0地址空间是不可访问的。
(2).text(代码段) .rdata(只读数据段)
实质上这两个段不是一个,是紧挨着的两个段,但是由于他们的属性相同,所以我们在此混为一谈。我们都知道指令在编译的时候放在.text中。
我们有如下一个定义:
char *p = "hello world"
*p = 'a';
他的含义是在函数里面,定义一个局部变量指针在栈上,指向了一个常量字符串"hello world"
但是由于p是放在.rdata段,**编译时没有问题但是运行不了,**因为只能读但是不能写。
所以一般的只能写成
const char *p = "hello world"
(3).data.bss数据段
**.data专门存放初始化了的,而且初始化值不为0的,.bss存放未初始化和初始化为0。**例如:
int gdata;
cout<<gdata<<end
以上代码我们会输出0,因为操作系统会把.bss里面的数据全部初始化为0
(4).heap
程序运行了过后调用new或者malloc才会分配堆内存(从低地址到高地址)
(5).stack
函数运行产生线程池,每一个线程独有的栈空间(从下往上)
(6)命令行参数和环境变量
命令行参数:程序运行会传入 比如Linux中的.\a.out 192.168.1.100 9090
环境变量,程序在搜索头文件搜索库文件默认的路径
4、具体空间指向
本篇文章开始的时候说给出的代码与进程虚拟地址空间对应如下:
(1)编译产物
在main函数之外的都成为数据,会生成符号,但是main函数里面的局部变量会生成指令,具体的操作如下:
int a = 20;
对应的指令是:mov dword ptr[a],0Ch
局部变量在编译的时候会生成指令在.test段,这条指令运行的时候系统会在当前进程的栈上开辟一个栈帧。开辟一个四字节的空间存放12这个整数
(2)对应区域
由上图的颜色我们可以一一对应。
(3)每一个进程的用户空间是私有的,但是内核空间是共享的
如下图所示,当我们有qq,酷狗和vs同时在线的时候,他们的进程虚拟地址空间如下:
每个进程的用户空间是相互隔离的,但是内核空间都是共享的。