开局一张图,内容全靠编。(开个玩笑,内容当然不是编的)
程序运行之前
程序代码编写完之后,如果要运行程序,我们还需对程序做编译工作。
编译过程:
1、预处理:头文件展开、宏定义展开(替换宏)、条件编译。
2、编译:检查语法,将预处理后文件编译生成汇编文件。
3、汇编:将汇编文件生成目标文件,即二进制文件。
4、链接:将目标文件链接为可执行程序。
复制代码
可执行程序分为三段,分别为代码段 text
、已初始化数据段 data
和 未初始化数据段 bss
3个部分。
可以将这三段分为两种,一种是 程序指令 段即代码段 text
。另一种则是 程序数据 ,包含数据段 data
和 未初始化数据段 bss
。
* 代码段 text 段: 存放程序代码,编译时确定,只读。
* 数据段 data 段: 存放在编译阶段就能确定的数据,可读可写。
这个就是常说的 静态存储区,存放 初始化了的的全局变量和静态变量,
还有常量(这个只读)。
* 未初始化数据 bss 段: 存放已经定义了的,但是没有初始化的全局变量和静态变量。
复制代码
程序运行之后
程序加载到内存前,代码区和数据区的大小就是固定地,程序运行期间不能改变。运行可执行程序时,操作系统加载硬盘到内存,根据可执行程序的信息,分出代码区(text),数据区(data),未初始化的数据区(bss)之外,还额外增加了 栈区 和 堆区。
栈区 :由系统进行内存的管理。主要存放函数的参数及局部变量。在函数完成执行,系统自行释放栈区内存,不需要用户管理。静态内存分配,编译时确定大小。是一种连续存储的数据结构,遵守 先进后出
原则。
堆区 :由 coder
手动申请,手动释放的内存区。如果不手动释放,只有等程序结束后由系统回收。容量远远大于栈区,但是速度比栈区慢,用于做动态内存分配,运行时确定大小。位于 bss
和 stack(栈区)
之间。不连续的存储结构,遵守 先进先出
原则。
内存的分配
内存分配方法 void *malloc(size_t __size)
参数类型为 size_t
,其实是 unsigned long
无符号长整型类型。 __size
是需要开辟的内存字节数,比如 malloc(10)
,这是在堆区开辟了十个字节长的空间。
返回值类型是 void *
,这是 泛型指针
,可以赋值给任何一种指针类型的变量。malloc
方法有一定的几率会失败,失败时返回一个 NULL
空指针。
void free(void *)
在使用完了 malloc
分配的堆空间内存之后,要对其进行释放,避免内存泄漏。
内存的分配方向
由最前的面的图可以看出,栈区stack 位于高地址位,堆区位于低地址位。
栈区
void func (void) {
int a;
int b;
printf("a: %p ,b: %p \n",&a,&b);
}
复制代码
在一个函数 func
中定义了两个变量 a
和 b
,函数位于栈区,我们打印两者的地址得到的结果 a: 0x7ffeefbff5bc ,b: 0x7ffeefbff5b8
,a
的地址比 b
的大,我们也知道代码是一行行从上往下执行的,所以说栈区的内存增长方向,是 从高位向低位增长
。
堆区
int *p = malloc(4);
int *q = malloc(4);
printf("p: %p ,q: %p \n",p,q);
复制代码
使用 malloc
方法,在堆区申请内存,得到的结果是 p: 0x10052ab50 ,q: 0x10052af70
,p
的地址比 q
的小,说明堆区内存增长方向,是 从低位向高位增长
。