1.1 程序和进程的概念
程序 - 硬盘上的可执行文件
进程 - 在内存中运行的程序
同一个程序可以同时对应多个进程,也就是同一个程序可以执行很多次
·
--------------------------------TEXT-------------------------------------------------
(1)代码区(进程先到这个区域)(Text)
- 存储功能代码,函数名所在的区域,函数指针指向该区域,存放可执行的代码指令
(2)只读常量区(Text)
- 存放字符串常量,以及const修饰的已经初始化的全局变量和const修饰的静态局部变量,字面值
--------------------------------DATA-------------------------------------------------
(3)全局区/数据区(Data)
- 存放没有const修饰的已经初始化的全局变量和static修饰的局部变量
(4)BSS段
- 存放没有初始化的全局变量和静态局部变量,该区域会在main函数执行之前进行自动清零
--------------------------------HEAP-------------------------------------------------
(5)堆区
- 使用malloc/calloc/realloc/free函数处理的内存,该区域的内存需要程序员手动申请和手动释放
--------------------------------STACK-------------------------------------------------
(6)栈区
- 存放局部变量(包括函数的形参)、const修饰的局部变量,以及块变量,该区域的内存由操作系统负责分配和回收,程序员尽管放心使用即可
总结:
(1)按照地址从小到大进行排列,进程中的内存区域依次为:代码区、只读常量区、全局区/数据区、BSS段、 堆区、栈区
(2)其中代码区和只读常量区一般统称为代码区;其中全局区/数据区和BSS段一般统称为全局区/数据区
(3)栈区和堆区之间并没有严格的分割线,可以进行微调,并且堆区的分配一般按照地址从小到大进行,而栈区的分配一般按照地址从大到小进行分配
1.2 字符串的存储形式之间的比较
对于指向常量字符串的字符指针和字符数组来说,字符指针可以改变指向,不可以改变指向的内容;对于字符数组来说,可以改变指向的内容,不可以改变指向
对于存放常量字符串的堆区来说,指针的指向和指针指向的内容都可以改变
1.3 虚拟内存管理技术
Unix/linux系统中的内存都是采用虚拟内存管理技术进行管理的,即:每个进程都有0~4G的内存地址(虚拟的,并不是真实存在的),由操作系统负责把虚拟内存地址和真实的物理内存映射起来,因此,不同的进程中虚拟地址空间看起来是相同的,但是对应的物理内存是不同的
其中0~3G之间的虚拟地址空间叫做用户空间,其中3G~4G之间的虚拟地址空间叫做内核空间;用户程序一般运行在用户空间,内核空间只有系统内核才可以访问,用户程序不能直接访问内核空间,但是系统内核提供了一些系统函数负责将程序从用户空间切换到内核空间执行,执行完毕之后再切换回到用户空间
内存地址的基本单位是字节,内存映射的基本单位是内存页,目前主流的操作系统的内存页大小是4Kb(4096字节)
1.4 段错误的由来
(1)scanf函数的使用
scanf("%d",&num)
=> scanf("%d",num);
=> 引发段错误
(2)指针的使用
int* pi; => 野指针
int* pi = NULL; => 空指针
scanf("%d",pi);
=> 引发段错误
(3)使用未映射的虚拟内存地址
vi a.c文件:
int num = 10;
printf("&num = %p\n",&num); //0x01
vi b.c文件:
int* pi = 0x01;
printf("*pi = %d\n",*pi); => 段错误
(4)对没有操作权限的内存进行操作
对只读常量区进行写操作,可能引发段错误
1.5 使用malloc函数申请动态内存
(1)使用malloc申请动态内存的注意事项
使用malloc函数申请内存时,除了申请所需要的内存大小之外,可能还会申请额外的12个字节,用于存储一些管理内存的相关信息,比如内存的大小等等
使用malloc申请的内存,一定要注意不要对所申请的内存空间进行越界访问,避免造成数据结构的破坏
(2)使用malloc申请内存大小的一般原则
一般来说,使用malloc申请比较小的动态内存时,操作系统会一次性分配33个内存页的大小,最终的目的就是为了提高效率而已
=> 获取当前进程的进程号
cat /proc/进程号/maps
=> 查看指定进程的内存分配情况
使用命令size查看程序的内存分配情况:
如:
size a.out
结果如下:
text(代码区) data(数据区) bss(BSS段)
1350 264 8
dec(十进制的总和) hex(十六进制的总和)
1622 656
filename(文件名)
a.out
1.7 内存管理的相关函数
(1)getpagesize函数
函数功能:
主要用于获取当前系统中内存页的大小,一般为4Kb
(2)sbrk函数
#include <unistd.h>
void *sbrk(intptr_t increment);
函数功能:主要用于调整内存的大小
参数increment > 0,则表示申请内存空间
参数increment < 0,则表示释放内存空间
参数increment = 0,则表示获取内存空间的当前位置
返回值:
成功返回调整内存大小之前的位置,失败返回(void*)-1
注意:该位置是分布或者是释放内存时,开始时的地址
注意:
一般来说,使用sbrk申请比较小的内存时,系统默认分配一个内存页的大小,一旦申请的内存超过一个内存页时,则再次分配一个内存页(也就是按照一个内存页为基本单位进行分配),而释放内存时,动态内存释放完毕时,系统不会保留任何动态内存映射
使用sbrk申请内存时,不会申请额外的空间存储管理内存的相关信息
使用sbrk函数申请内存比释放内存更加方便
(3)brk函数
int brk(void *addr);
函数功能:
主要用于根据参数指定的目标位置调整内存大小
如果目标位置 > 之前的目标位置 => 申请内存
如果目标位置 < 之前的目标位置 => 释放内存
如果目标位置 = 之前的目标位置 => 内存不变
注意:
使用brk函数释放内存比较方便,因此一般情况下都使用sbrk函数和brk函数搭配使用,使用sbrk函数负责申请内存,使用brk函数负责释放内存
1.7 内存管理的相关函数
(1)getpagesize函数
函数功能:
主要用于获取当前系统中内存页的大小,一般为4Kb
(2)sbrk函数
#include <unistd.h>
void *sbrk(intptr_t increment);
函数功能:主要用于调整内存的大小
参数increment > 0,则表示申请内存空间
参数increment < 0,则表示释放内存空间
参数increment = 0,则表示获取内存空间的当前位置
返回值:
成功返回调整内存大小之前的位置,失败返回(void*)-1
注意:该位置是分布或者是释放内存时,开始时的地址
注意:
一般来说,使用sbrk申请比较小的内存时,系统默认分配一个内存页的大小,一旦申请的内存超过一个内存页时,则再次分配一个内存页(也就是按照一个内存页为基本单位进行分配),而释放内存时,动态内存释放完毕时,系统不会保留任何动态内存映射
使用sbrk申请内存时,不会申请额外的空间存储管理内存的相关信息
使用sbrk函数申请内存比释放内存更加方便
(3)brk函数
int brk(void *addr);
函数功能:
主要用于根据参数指定的目标位置调整内存大小
如果目标位置 > 之前的目标位置 => 申请内存
如果目标位置 < 之前的目标位置 => 释放内存
如果目标位置 = 之前的目标位置 => 内存不变
注意:
使用brk函数释放内存比较方便,因此一般情况下都使用sbrk函数和brk函数搭配使用,使用sbrk函数负责申请内存,使用brk函数负责释放内存