编译连接过程
磁盘:
内存:
- 虚拟内存
- 物理内存
虚拟地址空间
用户进程部分分段内容:
名称 | 存储内容 |
---|---|
栈 | 局部变量、函数参数、返回地址 |
堆 | 动态分配内存 |
BSS段 | 未初始化或者初始值为0的全局变量或者局部静态变量 |
数据段 | 已初始化且初始化值不为0的全局变量或者局部静态变量 |
代码段 | 可执行代码、字符串字面值、只读变量 |
数据段与BSS段的区别如下
-
BSS段不占用物理文件尺寸,但占用内存空间;数据段占用物理文件,也占用内存空间对于大型数组如int ar0[10000] = {1, 2, 3, …}和int ar1[10000],ar1放在BSS段,只记录共有10000*4个字节需要初始化为0,而不是像ar0那样记录每个数据1、2、3…,此时BSS为目标文件所节省的磁盘空间相当可观。
-
当程序读取数据段的数据时,系统会出发缺页故障,从而分配相应的物理内存;当程序读取BSS段的数据时,内核会将其转到一个全零页面,不会发生缺页故障,也不会为其分配相应的物理内存。
- 运行时数据段和BSS段的整个区段通常称为数据区。某些资料中“数据段”指代数据段 + BSS段 + 堆。
32位系统的虚拟地址空间为什么是4G?
一共有32条地址总线,能标志的最小空间是0x 0000 0000
能标志的最大空间是0x ffff ffff
程序哪些会生成指令,哪些会生成数据?
#include<stdio.h>
int data1;
int data2 = 0;
int data3 = 10;
static int data4;
static int data5 = 0;
static int data6 = 20;
int main()
{
int a;
int b = 0;
int c = 30;
static int data7;
static int data8 = 0;
static int data9 = 40;
return 0;
}
数据:全局变量、静态变量
指令:局部变量,函数名
.data段中存放初始化并且不为0的全局变量和初始化并且不为0的静态变量。
.bss存放未初始化或初始化为0的全局变量,和未初始化或者初始化为0的静态变量。
编译链接过程
其实编译时就只是确定了全局变量(静态局部变量也算)的虚拟地址
初始态为进程准备阶段,常与就绪合并 ,就是在这个初始态阶段,做了很多准备工作,其中一项就是根据全局变量的虚拟地址映射物理地址为其分配空间,并初始化好。所以全局变量在程序进入main()函数之前,就已经在内存中存在了,而局部变量和堆区变量就只有程序执行到声明(或定义)它的那一条语句,才开始为其申请分配空间。
编译链接具体过程如下:
二进制可重定位文件的组成
查看头的信息:
Ubuntu中64位以02表示
-
section header
指令:readelf -S main.o
局部变量既不在data中,也不再bss中
符号表
强、弱符号
① 同名的强符号只能有一个,否则编译器报"重复定义"错误。
② 允许一个强符号和多个弱符号,但定义会选择强符号的。
③ 当有多个弱符号相同时,链接器选择最先出现那个,也就是与链接顺序有关。
对于C语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号(C++并没有将未初始化的全局符号视为弱符号)。
我们也可以通过GCC的"attribute((weak))"来定义任何一个强符号为弱符号。
注意,强符号和弱符号都是针对定义来说的,不是针对符号的引用
//main.c
#include<stdio.h>
short a = 10;
short b = 20;
void fun();
int main()
{
fun();
printf("a = %d\n",a);
printf("b = %d\n",b);
return 0;
}
//fun.c
int a;
void fun()
{
a = 100;
}