【C语言】新年第一篇:C语言内存管理

新年开工文章

emmm,细细总结去年(从业IT第一年),多多少少还是积累了不少编程的知识,由于长期使用C++,对于内存的理解或多或少有了一点点自己的理解,今年是2021年,也是本命年,争取在工作上有一些大的突破,在学习上有一些小的促进,相辅相成,不至于被行业进程所淘汰。

想学习很久的STL也可以在今年慢慢着手去学习了,相较去年,对于自己公司的业务及项目流程梳理也有了一定的认知,不再是去年刚入公司那个小菜鸟,相信学习的时间也会比去年多多了,新年新生活,编程步步升。

大道至简,一点点的积累,希望在以后的创业道路上积淀起稳健的港湾。

01、C语言内存管理图

在C++专栏也有过一篇内存管理,图跟本篇图相差无几,如下:
在这里插入图片描述

02、内存区域

2.1、内存分区

C源代码进过预处理、编译、汇编和链接4步生成一个可执行程序。
程序在没有运行之前,也就是说程序没有被加载到内存前,可执行程序内部已经分好3段信息,分别是代码区(text)、数据区(data)和未初始化数据区(bss)三个部分。(部分人直接把data和bss合起来叫做静态区或全局区)。
运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区、数据区和未初始化数据区之外,还额外增加了栈区和堆区。

2.2、区域说明
代码区

代码区存放CPU执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),因为对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,为了防止程序意外地修改了它的指令。

数据区

数据区则相对来说比较复杂一点,可以细分为:全局初始化数据区/静态数据区(data区)、未初始化数据区(bss区)。

data区

该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。

bss区

存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为0或者空(NULL)。

堆区

堆是一个大容器,是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间,堆在操作系统对进程初始化的时候分配,运行过程中也可以像系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。它的容量远大于栈,用于动态内存分配,堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若不主动释放,程序结束时,由操作系统回收。堆区通常加载音频文件、视频文件、图像文件、文本文件以及大小超过栈大小由程序员主动申请分配的内存的大数组等。

内存映射区

内存映射的概念这里简单提一下:例如在一个进程中,一堆数据比较大,要经常去读写操作他们,这时候,你放在实际的堆区、栈区去操作时很浪费效率的,所以,我们通过进程间的内存映射可以实现在虚拟内存中找一块儿内存来装这些数据,但是也有一个缺点,就是内存映射区存储的数据一般来说是4k的整数倍,不然会有分段的情况,也会导致效率比较低,所以,选择的时候可以具体情况具体分析,至于怎么使用内存映射,emmmm,我也不是很了解,用得太少,怕说错,大家感兴趣可以去查查或者看下STL中mmap的内存映射机制,可能会有一些具体的方法。

栈区

栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。不同的操作系统分配给每一个程序的栈区大小不同:一般Windows是1M ~ 8M不等,一般Linux是1M ~ 16M不等。

栈区的最大空间是可以修改的,Linux通过ulimit命令可以更改,Windows可以通过编译器去修改(不同编译器设置不一样,可以自行百度)。

内核区(内核空间)

对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间)为 4G(2的32次方)。也就是说一个进程的最大地址空间为 4G。操作系统的核心是内核(kernel),它独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证内核的安全,现在的操作系统一般都强制用户进程不能直接操作内核。具体的实现方式基本都是由操作系统将虚拟地址空间划分为两部分,一部分为内核空间,另一部分为用户空间。针对 Linux 操作系统而言,最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用,称为内核空间。而较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用,称为用户空间。

对上面这段内容我们可以这样理解:
每个进程的 4G 地址空间中,最高 1G 都是一样的,即内核空间。只有剩余的 3G 才归进程自己使用。
换句话说就是, 最高 1G 的内核空间是被所有进程共享的!
下图描述了每个进程 4G 地址空间的分配情况:
在这里插入图片描述
既然说到这里,思考一个问题:为什么要区分内核态和用户态?

在 CPU 的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。如果允许所有的程序都可以使用这些指令,那么系统崩溃的概率将大大增加。
 所以,CPU 将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令。比如 Intel 的 CPU 将特权等级分为 4 个级别:Ring0~Ring3。
 其实 Linux 系统只使用了 Ring0 和 Ring3 两个运行级别(Windows 系统也是一样的)。当进程运行在 Ring3 级别时被称为运行在用户态,而运行在 Ring0 级别时被称为运行在内核态。

区分内核态与用户态:当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。

在内核态下,进程运行在内核地址空间中,此时 CPU 可以执行任何指令。运行的代码也不受任何的限制,可以自由地访问任何有效地址,也可以直接进行端口的访问。

在用户态下,进程运行在用户地址空间中,被执行的代码要受到 CPU 的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中 I/O 许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。

对于以前的 DOS 操作系统来说,是没有内核空间、用户空间以及内核态、用户态这些概念的。可以认为所有的代码都是运行在内核态的,因而用户编写的应用程序代码可以很容易的让操作系统崩溃掉。

对于 Linux 来说,通过区分内核空间和用户空间的设计,隔离了操作系统代码(操作系统的代码要比应用程序的代码健壮很多)与应用程序代码。即便是单个应用程序出现错误也不会影响到操作系统的稳定性,这样其它的程序还可以正常的运行(Linux 可是个多任务系统啊!)。

所以,区分内核空间和用户空间本质上是要提高操作系统的稳定性及可用性。

03、简单程序区分内存划分

我们参照上面的图或者下面这张图写一段简单的程序,分别注释属于内存什么区域。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

int m_nOs = 1;  //全局变量  belong to data区
static m_nOs2 = 2;  //全局静态初始化数据  belong to data区

int m_nIs = 1;  //全局未初始化变量  belong to bss区
static m_nIs2 = 2;  //全局未初始化静态变量  belong to bss区

int function(size_t size /*参数属于栈区*/);  //function函数属于代码区,里面的变量一样遵循上图所示的规则

int main() 
{
	static m_nOs3 = 3; //局部静态初始化数据  belong to data区
	char * buffer = "hello world";  //字符串常量  belong to 文字常量区(也属于data区)

	int* p = (int*)malloc(sizeof(int)*n); //n:一个整数,必须明确整数是多大(不然报错) p属于栈区,但是分配的内存属于堆区
	
	int bRet = function(20);  //bRet属于栈区,20也属于栈区

	system("pause");
	return 0;
}

04、小结

  1. 所有未初始化的静态变量和全局变量,编译器会默认赋初值0。
  2. 程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。
  3. data段和bss区中的数据的生存周期为整个程序运行过程。
  4. data段、text区和bss区是由编译器在编译时分配的,堆和栈是由系统在运行时分配的。

本节内容不多,简单的温习C语言的内存管理,肯定是不全面的,学习C语言最重要的两个东西,一个是指针、一个就是内存管理。 指针说到底也是操作内存的变量而已,所以,内存有多重要,基本是C语言板块的60%~70%的比例。

以后内容会继续涉及到内存管理的东西,到时候再具体情况具体分析。

版权声明:转载请注明出处,谢谢!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cain Xcy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值