文章目录
liunx 内存管理
一、虚拟地址
任何一个程序,正常运行都需要内存资源,用来存储变量,常量,函数代码等,这些不同的内容,所存储的区域是不同的,且不同的区域有不同的特性,因此需要了解程序的内存布局,以及内存区域的特性。
每个c语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大的方便内核管理不同的进程,如下图,三个完全不相干的进程p1,p2,p3,它们很显然会占据不同的物理内存,但经过系统的变化和映射,它们虚拟内存的布局是完全一样的。
PM(Physical Memory):物理内存
VM(Virtual Memory):虚拟内存
随机将一个进程的虚拟内存放大来看,它会包含以下内容:
栈空间、堆空间、数据段、代码段
虚拟内存的典型布局
虚拟内存,内核区域对于应用程序而言是禁闭的,它用于存储操作系统的关键性代码,另外由于Linux系统的历史原因,在虚拟内存的最低端0x0000 0000 ~ 0x0804 8000之间有一段禁闭区域,该区域不可访问。
二、进程内存布局
1.栈内存:
按照”后进先出“的原则来操作;栈全称”运行时栈(run-time stack)",栈会随着进程运行时不断发生变化,一旦有新的函数被调用,就会立即在栈顶分配一帧内存,专门用于存放该函数内定义的局部变量(包括所有的形参),当一个函数执行完毕返回之后,它所占用的那帧内存将被立即释放。
栈主要是用来存储进程执行过程所产生的局部变量(为了实现函数的嵌套调用和返回,栈还必须包含函数切换时当下的代码地址和相关寄存器的值,这个过程被称为”保护现场“,等被调函数执行结束后,再”恢复现场“)。栈的大小一般为8MB,超过了这个最大值会产生所谓的”栈溢出“导致程序崩溃。所以:在进程中不宜嵌套调用太深,也不要定义太多太大的局部变量。
2.堆内存:
堆内存是一块自由内存,原因是在这个区域定义和释放变量完全由你来决定,即所谓的自由区。堆跟栈的最大区别在于是不设置限制,最大值取决于系统的物理内存。
堆的全称是”运行时堆(run-time heap)“,跟栈一样,会随着进程的运行而不断增大或缩小。
3.数据段:
数据段实际上分为三部分,地址从高到低分别是.bss段、.data段和.rodata段
.bss段:
存放未初试化的静态数据,它们都将会被初试化为零
.data段:
存放已初始化的静态数据,这些初始值从程序文件拷贝而来
.rodata段:
放只读数据,即常量,比如进程中所有的字符串、字符常量、整型浮点型常量等。
注意:
静态数据指的是 所有的全局变量,以及static型局部变量
4.代码段:
代码段实际上也至少分为两部分:.text段和.init段
.text段:
存放用户代码,包括main函数在内的所有用户自定义函数
.ini段:
存放系统给每一个可执行程序自动添加的”初始化“代码,这部分代码功能包括环境变量的准备、命令行参数的组织和传递等,并且这部分数据被放置在栈底。init段是存放的系统初始化,这部分代码之所以要放在.init段是这个段当中的代码默认只会执行一遍(初始化只能执行一遍),完成任务之后所占据的内存会被立即释放,以便节省系统资源。
注意:
(1)栈中的环境变量和命令行参数,在程序一开始就固定在栈底(即紧挨着内核的地方),且在整个进程运行期间中不再发生变化,若进程运行时对环境变量的个数或者值做了修改,则为了能够容纳修改后的内容,新的环境变量将会拷贝放置在堆中
(2)堆和栈都是动态变化的,分别向上和向下增长,大小随着进程不断变大变小。
(3)数据段的大小在程序一开始就固定了,没有初始化的静态数据(所有全局变量,以及static型局部变量),.bss段就会默认给这些数据初始化为0
(4)尽量避免使用静态数据:①静态数据的生命周期和整个进程相当(在进程退出之前它们都不会释放),会一直占用内存。 ②静态数据是一直典型的共享资源,尤其在多线程程序中使用全局变量,会产生”竞态“。若要使用需同步互斥手段进行包护
三、堆(heap)
堆内存被称为内存中的自由区,在此区域内存的生命周期我们是可控的;对比其他区域的内存,如栈内存,栈的特点是临时分配临时释放,一个变量如果是局部变量,它就会被定义在栈内存中,一旦这个局部变量所在的函数退出,就会被立即释放;再如静态数据,它们都是被存储在数据段,这些变量会一直占用内存直到进程退出为止。
1.堆内存的生命周期
从malloc()/calloc()/realloc()开始,到free()结束。
2.堆内存的申请和释放(堆内存操作API)
malloc()、free() 、calloc()、realloc()
2.1 malloc()
#include <stdlib.h> //头文件
void *malloc(size_t size);
//功能:在堆中申请一块大小为size的连续内存
//参数:size:内存大小(字节)
//返回值:成功,返回新申请的内存基地址;失败,返回NULL
//备注:该函数申请的内存是未初试化的
2.2 free()
void free(void *ptr);
//功能:将指针ptr所指向的堆内存释放
//返回值:无
//备注:参数ptr必须是malloc()/calloc()/realloc()的返回值
2.3 calloc()
void *calloc(size_t nmemb, size_t size);
//功能:在堆中申请一块具有nmemb个元素的匿名数组,每个元素大小为size
//参数:nmemb:有多少个;size:每个元素的大小
//返回值:成功,返回新申请的内存基地址;失败,返回NULL
//备注:该函数申请的内存将被初试化为0
2.4 realloc()
void *realloc(void *ptr, size_t size);
//功能:将ptr所指向的堆内存大小扩展为size
//参数:nmemb:有多少个;size:每个元素的大小
//返回值:成功,返回扩展后的内存基地址;失败,返回NULL
//备注:①返回的基地址可能与原地址ptr相同,也有可能不同(发生了迁移)②当size为0是,该函数相当于free(ptr);③如果ptr原来是NULL,就相当于malloc(size)
注意:
使用realloc()时,如果新size大于原来申请的size,也就是扩大内存操作,则会把ptr指向的内存的数据拷贝到新地址;如果新size小于原来申请的size,也就是缩小内存操作,则原数据会被拷贝并截取到新size的位置。如果使用realloc()时申请内存失败,返回NULL,原来的ptr指针依然可以用。
四、作用域
C语言中,标识符都有一定的可见范围,这些可见范围保证了表示符只能在一个有限的区域内使用,这个可见范围,被称为作用域
在软件开发中,尽量缩小标识符的作用域是一项基本原则,一个标识符的作用域超过它实际所需要的范围时,就会对整个软件的命名空间造成污染,导致一些不必要的名字冲突和误解。
1.函数声明作用域:
在函数声明表达式中定义的变量,其可见范围仅限于该函数声明式
void func(int a,char *s);
注意:
变量a和s只能在函数声明式中可见
函数声明,变量a和s可以省略(一般不省略,因为可以对参数注解)
2.局部作用域
int a=10;
{
int a=20;
printf("a = %d",a);
}
注意:
代码块是指用{}括起来的区域
代码块可以嵌套包含,外层的标识符会被内嵌的同名标识符临时覆盖变的不可见
代码块作用域的变量,由于其可见范围是局部的,因此称为局部变量
3.全局作用域
在代码块外部定义的变量,其可见范围可以跨越多个文件
test.c
int lable; //lable除了本文件可见,还可以被作为外部引用
extern in lable; //外部引用这个变量,这个变量在test.c文件里
int main()
{
lable=20;
printf("lable = %d",lable);
return 0;
}
小结:
代码块作用域:(局部变量,静态局部变量)
从变量定义或者声明开始,到当前这个代码结束(作用域之外,不能再次使用)
文件作用域:(全局变量)
从全局变量的定义或声明位置开始,整个文件都可以用
函数原型作用域:(当前文件的普通函数)
从当前函数定义或声明位置开始时,整个文件可以用
goto标签: 包含它的当前函数任意位置都可以跳转,不同函数之间是不行的,只作用域包含它的函数
量)
五、变量存储期(生命周期)
C语言中,变量都是由一定的生存周期的,所谓的生存周期指的是分配到释放的时间间隔,为变量分配内存相当于变量的诞生,释放放其内存,相当于变量死亡,从诞生到死亡就是一个变量的生命周期
根据定义方式的不同,变量的生命周期有三种形式:
自动存储期
自定义存储期
静态存储期
自动存储期:
在栈内存中分配的变量,统统拥有自动存储期,因此也都称为自动变量,这里自动的含义,指的是这些变量的内存管理不需要开发者操心,都是自动的,在变量定义处自动分配,出了变量的作用域就自动释放
以下三个概念是等价的
自动变量:从存储期的角度,描述变量的时间特性
临时变量:同上
局部变量:从作用域的角度,描述变量的空间特性
可以直接统称为栈变量
静态存储期:
在数据段中分配的变量,统统拥有静态存储期,因此也都被称为静态变量,这里静态的含义,指的是这些变量不会因为程序运行而发生临时分配和释放。它们的生命周期是恒定的,跟整个程序一致的
静态变量包含:
全局变量,不管加staic关键字还是不加,都是静态变量
静态局部变量
注意:
若定义时未初始化,系统会将所有的静态数据自动初始化为0
静态数据初始化,只会执行一遍
静态数据从程序开始运行时便已经存在,直到程序退出时才释放
static修饰局部变量,使之从栈内存临时数据,变成静态数据
static修饰全局变量,使之由各个文件可见的静态数据,变成本文件可见的静态数据
自定义存储期
在堆中分配的变量,统统拥有自定义存储期,也就说说这些变量的分配和释放,都是由开发者决定的,由于堆内存拥有高度自治权,因此堆是程序开发中用得最多的一片区域
相关的API
申请堆内存:malloc(),calloc(),realloc()
清零:bzero()
int *p = malloc(20);
bzero(p, 20);
释放内存:free()
注意:
malloc申请的堆内存,默认情况下随机值,一般需要用bzero()来清零
calloc申请的堆内存,默认情况下是已经清零了的,不需要再清零
free只能释放堆内存的空间,不能释放别的区段的内存
释放内存的含义:
释放内存意味着将内存的使用权归还给系统
释放内存并不会改变指针的指向
释放内存并不会对内存做任何修改,更不会将内存清零
小结:
自动存储期:栈空间里面的数据
普通局部变量,函数的形参
在定义位置开始,出作用域结束
静态存储期:数据段里面的数据
静态局部变量,全局变量
在定义位置开始,出程序退出结束
自定义存储期:堆空间里面的数据
由malloc(),calloc(),realloc()申请成功开始,到free()释放结束
六、链接类型
①外部链接:
普通的全局变量,函数,对外公开
②无链接:
普通局部变量(包含静态局部变量),函数形参
③内部链接:
staic修饰的全局变量,函数,对外不公开,对内公开
普通的全局变量,函数,要想在外部被引用,需要用到extern关键字,做外部声明,表示声明的变量或函数在其他文件有定义。
多个文件编译:
gcc file1.c file2.c ... -o 可执行文件(目标文件)
七、static关键字
static关键字的作用
修饰全局变量:
普通全局变量------》静态全局变量
数据段 ------------》数据段
静态存储期--------》静态存储期
外部链接 ----------》内部链接
修饰局部变量:
普通局部变量------》静态局部变量
栈空间 ------------》数据段
自动存储期--------》静态存储期
无链接 ------------》无连接
修饰函数:
普通函数----------》静态函数
外部链接----------》内部链接