1. 指令与数据
指令是指挥计算机工作的命令,在计算机取指阶段从内存中读出的指令流,由控制器处理;数据是各种字母,数字,符号和图像等,在计算机执行阶段从内存中读出的数据流,由CPU运算器处理。
2. 虚拟内存与虚拟地址空间
虚拟内存是操作系统为了解决内存耗尽问题而从硬盘上分出一部分空间充当内存使用。例如,执行某个较大程序时,需要占用较大的内存空间,运行内存耗尽,计算机就会调用硬盘上的硬盘空间给该程序使用,缓解了内存紧张问题。
虚拟内存大小与CPU位数有关,CPU位数是指一次能加载运行的整数的宽度,也可以定义为ALU算术逻辑单元的宽度,或者是数据总线的条数。
注意:虚拟内存是不存在的,是逻辑上假设出来的。
虚拟地址空间是指计算机在创建一个进程时,会为其分配4GB的可访问的地址空间。每个进程都由其自身的虚拟地址空间,范围从0x00000000到0xFFFFFFFF,但不可访问其他进程的虚拟地址空间。虚拟地址空间如下图所示:
每个程序都会与一个4G的虚拟地址空间,0~3G均为用户空间(私有),1G为内核空间(共享)。在一个用户空间定义一个指针可以指向其他程序的地址,但不能访问其地址,因为用户空间私有,只能指向该程序地址。
二、编译和链接
预编译阶段:可理解为执行文本处理,主要处理源代码中以“#”开头的预编译指令。例如,展开所有的宏定义;处理#include预编译指令是将所包含的文件插入到预编译指令处;删除所有的注释等等。
编译阶段:进行词法分析,语法分析,语义分析,并且汇总所有的符号。最后生成相应的汇编代码文件。
汇编阶段:将汇编代码转换成机器可执行的二进制指令。最后生成目标文件。
链接阶段:(1)按照相同属性合并.obj文件的段,并调整段的偏移及段的长度;(2)合并符号表,进行符号解析,使每一个符号均找到定义的地方;(3)进行符号重定位。因为编译时各自编译各自,外部数据,调用函数等均使用错误的地址,符号重定位就是修改这些地址,数据使用绝对地址,函数则使用下一条指令的偏移量,最后是程序执行时均能找到正确的地址,顺利执行。
三、目标文件
目标文件从结构上讲是可执行文件格式,只是还没有经过链接处理,有些符号和地址还没有经过修正,但按照可执行文件存储。在window下可执行文件主要是PE格式,在Linux下可执行文件主要是ELF格式。这里,我们只讨论ELF格式。
举例:
#include<stdio.h>
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 d = 13;
static int e = 0;
static int f;
return 0;
}
(1)查看ELF文件组成:objdump -h main.o
(2)查看ELF文件头:readelf -h main.o
由这张图可得:ELF格式文件入口地址为0x0,文件头大小为52字节,即存储位置从0x34开始。
(3)查看目标文件符号表:objdump -t main.o
由此图可以看出.data数据段只存储全局变量gdata1,静态全局变量gdata4和静态局部变量d,因此.data段只有12个字节;.bss数据段存储gdata2,gdata5,gdata6,f,e,因此证明.bss段大小有20个字节。
那数据怎么区分存储在哪个区域?全局变量gdata3没有存储呢?
初始化的及初始化不为0的存放在.data区,未初始化及初始化为0的存放在.bss区。
因为C语言有强符号(有初始化)和弱符号(没有初始化)之分,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。多个相同符号选择强符号,多个同名弱符号选内存占用最大的。全局变量int gdata3 为弱符号,弱符号在common块存储,编译时不确定用此gdata3,只有链接时才确定使用此处gdata3。
四、链接