高级C语言-内存

本文介绍了计算机程序运行与内存的关系,讲解了冯诺依曼和哈佛结构,动态内存DRAM与静态内存SRAM的区别,并探讨了内存管理的重要性,包括操作系统和编程语言层面的内存管理策略。此外,还提到了内存的位宽、编址方式以及数据类型与内存的关系,最后讨论了数组、指针、栈、堆和数据结构在内存管理中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内存

(1)程序运行的目的
程序的目的就是为了去运行,程序运行是为了得到一定的结果,计算机就是为了计算,所有的计算机就是为了计算,计算数据。计算机程序 = 代码 + 数据。
宏观理解:代码就是动作,就是加工数据的动作,数据就是数字,就是代码加工的东西。
代码 + 数据 = 结果
用函数来类比:函数的形参就是代加工的数据;函数本体是代码,函数的返回值就是结果,函数体的执行过程的过程。

int add(int a,int b)
{
	return a+b;
}//这个函数即看重过程也看重结果
void add(int a,int b)
{
	int c;
	c = a + b;
	printf("c= %d.\n",c);
}//这个函数只看重过程

(2)计算机程序运行过程
计算机程序的运行过程,其实就是程序很多个函数相继运行的过程。程序是由很多函数组成的,程序的本质就是函数,函数的本质就是加工数据的动作
(3)冯洛伊曼结构和哈弗结构
冯洛伊曼结构:数据和代码放在一起
哈弗结构:数据和代码分开存放
代码:函数就是代码
数据:全局变量和局部变量
在SPV210中运行的linux系统上,运行程序时:这时候所有的应用程序的代码和数据都在DRAM,所以这个结果就是冯洛伊曼结构;在单片机中,我们吧程序代码烧写到Flash(内置的NorFlash)中,然后程序在Flash中原地运行,程序中涉及的数据(全局变量,局部变量)不能放在Flash中,必须放在RAM(静态的SRAM)中,这个结构称为哈佛结构。
(4)动态内存DRAM和静态内存SRAM
动态随机存取存储器(Dynamic Random Access Memory,DRAM)
静态随机存取存储器(Static Random-Access Memory,SRAM)
(5)为什么需要内存
内存时用来存储可变数据的,数据在程序中表现为全局变量,局部变量等(在gcc中,其实常量也是存储在内存中)(大部分单片机中,常量时存储在Flash中,也就是代码段)
我们了解的很多编程的关键就是为了内存,譬如说数据结构(数据结构是研究数据如何组织,数据是放在内存中)和算法(为了更优秀的方式来加工数据,既然数据有关就离不开内存)。
(6)如何管理内存
对于计算机来说:内存容量越大则可能性越大,所以大家都希望自己的电脑内存越大。我们写程序如何管理内存就成了很大的问题。如果管理不好,可能程序运行就会消耗大量的内存,这样迟早内存会不够,当没有内存时,程序就会崩溃。所以内存对程序来说是一种资源,所以管理内存对程序来说是一个重要的技术和话题。
先从操作系统来说
操作系统掌握所有的硬件内存,因为内存很大,所以操作系统把内存分块(4KB),然后以页面为单位来管理。页面内以更小的方式字节来管理。操作系统给我提供了内存管理的接口,我们只需要用API即可管理内存,例如C语言中的malloc,free这些接口来管理内存、
没有操作系统时:(裸机程序中),程序需要直接操作内存,编程者需要自己计算内存的使用和安排。如果不小心内存使用错误,错误的结果需要自己承担。
再从语言角度来说
例如汇编:根本没有任何内存管理,内存管理全靠程序员,汇编中操作内存时直接使用内存地址(例如:0xd0020010)
例如C语言:C语言中编译器帮我们管理内存地址,我们都是通过编译器提供的变量名等来访问内存,如果需要大片的内存,可以通过API(malloc free)来访问系统内存。裸机程序中需要自己来定义数据解决方法。
例如C++语言:C++语言对内存的使用进一步封装。我们可以用new来创建对象(其实就是为对象分配内存),让使用完了用delete来删除对象(其实就是释放内存)。所以程序员new了对象,但是用完了忘记delete就会照成这个对象占用的空间不能释放,这就是内存泄漏。
例如java/C#等语言:这些语言不直接操作内存,而是通过虚拟机来操作内存,这样虚拟机作为我们程序员的代理,来处理内存的释放工作。如果我的程序申请了内存,使用完后忘记释放,则虚拟机会帮我们释放内存。我们程序对性能非常在乎的时候(内核)就会用C/C++语言;当我们对开发程序的速度很在乎的时候,就会使用Java/C#等语言。
(7)位,字节,
内存单元的大小单位有4个:位(1bit),字节(8bit),半字(16bit),字(32bit)。在所有的计算中,位永远是1bit,字节永远是8bit。
(8)半字,字的概念
在历史上出现的16位,32位,64位系统中,操作系统有Windows,linux,ios中,被混乱使用的。
(9)内存位宽(硬件和逻辑两个角度)
从硬件角度来说:硬件内存的实现是有角度的,也就是说有些内存条就是8位的,而有些就是16位的。那么需要强调的是内存芯片之间是可以并联的,通过并联后即使8位的内存芯片也可以做出16位或32位的硬件内存。
从逻辑上讲:内存位宽在逻辑上式任意的,甚至逻辑上存在内存位宽是24位的内存(无意义)。不管内存位宽是多少,我直接操作是不影响的。但是因为你的操作不是纯逻辑而是需要硬件去执行的,所以不能为所欲为,所以我们实际的很多操作都是限于硬件特性的。
(10)内存编址方法
内存在逻辑上就是一个一个的格子,这些格子可以用来装东西(里面装的东西就是内存中存储的数),每个格子有一个编号,这个编号就是内存地址,这个内存地址(一个数字)和这个格子的空间(实质是一个空间)是一一对应且永久绑定的。这就是内存的编址方式。
(11)内存的编址是以字节单位
随便给一个数字,然后说这个数字是一个内存地址,然后问你这个内存地址对应的空间多大?这个大小是固定式,就是一个字节(8bit)。
如果把内存比喻一栋楼,那么楼里面的一个一个房间就是一个个的内存格子,这个格子的大小是固定的8bit,就像是楼里面的户型一样。
(12)内存和数据类型的关系:
C语言中的基本数据类型:char short int long float double
整形(整数类型,这个整就体现在它和CPU本身的数据位宽是一样的)32位的CPU,整形就是32位,int就是32位
数据类型是用来定义变量的,而变量需要存储,运算在内存中。所以数据类型必须和内存匹配才能获得最好的性能,否则可能不工作或效率低下。
在很多的32位环境下,我们实际定义bool类型变量(实际只需要1bit就够了)都是用int来实现bool的。也就是说我们定义一个bool;编译器帮我们分配了32位的内存来存储这个bool变量bl,编译器这么做实际浪费了31位的内存,但是好处是效率高。
(13)内存对齐
我们在C中int a;定义一个int 类型变量,在内存中就必须分配4个字节来存储这个a。有这么2中不同的内存分配思路和策略:

0 1 2 3对齐访问
1 2 3 4非对齐访问

内存的对齐访问不是逻辑问题,是硬件的问题。从硬件角度来说,32位的内存它 0 1 2 3 四个单元本省逻辑就有相关性,这4个字节组合起来当作一个int硬件上就是合适的,效率高。
对齐访问很配合硬件,所以效率高,非对齐访问因为和硬件本身不搭配,所以效率不高
(14)C语言对内存地址的封装
例如:C语言中 int a;a = 5;a += 4; //a == 9
int a;
编译器帮我们申请了1个int 类型的内存格子(长度是4字节,地址是确定的,但是只有编译器知道,我们是不知道的,也不需要知道),并且把符号a和这个格子绑定。

a = 5;//编译器发现我们要给a赋值,就会把这个值5丢到符号a绑定的那个内存格子中

C语言中数据类型的本质含义是:表示一个内存格式的长度和解析方法。

(int *) a;
(float *)a;
(short)a;
(char)a;//内存单元格子的编址单位是字节

C语言中数据类型的本质含义:表示一个内存格子的长度和解析方法。
数据类型决定长度的含义:我们一个内存地址(0x30000000),本来这个地址只是代表1字节的长度,但是实际上我们可以通过给他一个类型(int),让他有了长度(4),这样这个代表内存地址的数字(0x300000000)就能表示从这个数字(0x30000000)开头的连续的n(4)个字节的内存格子(0x30000000+0x30000001+0x30000002+0x30000003)
(14)用指针来间接访问内存
关于类型(不管是普通类型int float,还是指针类型int * float *等),只要记住:类型只是对后面数字或者符号(代表的是内存地址)所表征的内存的一种长度规定和解析方法规定而已,C语言中的指针,全名是指针变量,指针变量其实很普通变量没有任何区别。譬如:int a和int *p其实没有任何区别,a和p都代表一个内存地址(0x20000000),但是这个内存地址的长度和解析方法不同。a是int型所以a的长度是4字节,解析方法是按照int规定的;p是int * 类型的,所以长度是4字节,解析方法是int * 的规定来的(0x20000000开头的连续4字节中存储了1个地址,这个地址所代表的内存单元中存放的是一个int类型的数)
(15)数组来管理内存
数组管理内存和变量其实没有本质区别,只是符号的解析不一样(普通变量,数组。指针变量其实都是没有本质区别的,都是对内存地址的解析,只是解析方法的不一样)

int a;//编译器分配4字节的长度给a,并且把首地址和符号a绑定起来
int b[10];//编译器分配40个字节长度给b,并且把首元素首地址和符号b绑定起来

(16)数据结构
数组的优势:数组比较简单,访问下标,可以随机访问
数组的缺点:数组中所有元素类型必须相同,数组大小必须定义时给出,而且一旦确定就不能更改
结构体发明出来就是为了解决数组的第一个缺陷:数组所有元素类型必须相同。

int age[10];//数组
struct ages//结构体
{
	int age1;
	int age2;
	int age3;
};
struct ages age;
//在包中元素类型不同时只能用结构体而不能用数组
struct people
{
	int age;
	char name[10];
	int height;
};//各个元素类型不一样,必须使用结构体,没法用数组

结构体内嵌指针实现面向对象

struct s
{
	int age;//普通变量
	void (*pFunc)(void);//函数指针,指向void func(void)这类函数
}

栈管理内存的特点
先进先出 FIFO first in frist out 队列。
先进后出 FILO frist in last out 栈。
栈得到特点就是入口即出口,只有一个口,还有一个已经堵死。所以先进去的必须后出来。
队列的特点就是两头都是开口的,从入口进,从出口出,所以先进去的先出来,后进去的后出来。
分析一个细节:C语言中,定义局部变量如果没有初始化,则值是随机的,为什么?
定义局部变量,其实就是在栈中通过移动栈指针来给程序提供一个内存空间与这个局部变量绑定,因为这段内存空间在栈上,而栈内存是反复使用的(没有清零),所以说使用栈来实现的局部变量定义时如果不显示初始化,值就是脏的,如果显示初始化:int a = 15;相当于编译器int a;a = 15;
栈的管理
堆是一种内存管理方式。内存管理对操作系统来说是一种复杂的事情;首先内存容量很大;其次内存需求在时间和大小快上没有规律
堆这种内存管理方式特点就是自由(随时申请,释放;大小快随意)。堆内存是操作系统划归给堆管理器(操作系统中的一段代码,属于操作系统的内存管理单元)来管理,然后向使用者(用户进程)提供API(malloc和free)来使用堆内存
堆管理内存的特点
特点一:容量不限
特点二:申请及释放都需要手工释放。手工进行的意义是:需要程序员写代码明确进行申请malloc及free。在C/C++语言中,内存泄漏是最严重的bug。
C语言操作对内存的接口
堆内存释放时最简单,直接调用free释放即可。void free(void *ptr)
堆内存申请时,有3个可选这的类似功能的函数:malloc,calloc,realloc

void *malloc(size_t size);
void *calloc(size_t nmemb,size_t size);//nmemb个单元,每个单元size个字节
void *realloc(void *ptr,size_t size);//改变原来申请的空间的大小

譬如申请10个int元素的内存:

malloc(40);			malloc(10*sizeof(int));
calloc(40,4);		calloc(10,sizeof(int));

数组定义时必须要给出数组个数(数组大小),而且一旦定义再无法更改。堆内存申请时必须给定大小,然而一旦申请完成大小不变,想要改变,只能通过realloc接口
复杂数据结构
链表是最重要的,链表在linux内核中使用非常多,驱动,应用程序编写很多时候都需要使用链表,所以对链表必须掌握,会自己定义结构体来实现链表,会写链表的节点插入(前插,后插),节点删除,节点查找,节点遍历。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值