C语言中关于内存这个话题

来源:互联网 发布:java instanceof 编辑:程序博客网 时间:2024/06/05 20:34

1.程序为什么需要内存?

要知道计算机程序 = 代码+数据,而程序的目的是为了去运行,程序运行是为了得到一定的结果。计算机就是用来计算的,所有的计算机程序其实都是在做计算。计算就是在计算数据。所以计算机程序中很重要的部分就是数据。对于程序而说,程序的本质就是函数,函数的本质就是加工数据的动作。

内存就是存储可变的数据的,数据又可表现为全局变量和局部变量等,这对于程序的运行有着很大的关系。因此越复杂的程序要的数据就越多,也就是要的内存就越多,所以内存这时候就表现出至关重要的关系。这时候就有数据结构来提高内存的使用效率,同时算法也是为了更加优秀的方法来加工数据。

我们写程序时如何管理内存就成了很大的问题。如果管理不善,可能会造成程序运行消耗过多的内存,这样迟早内存都被你这个程序吃光了,当没有内存可用时程序就会崩溃。所以内存对程序来说是一种资源,所以管理内存对程序来说是一个重要技术和话题。

那怎么管理内存了?

①从操作系统角度(OS),裸机时,即无OS时,程序直接操作内存,因此程序需要严格计算内存,防止重叠。有OS时,操作系统给我们提供了内存管理的一些接口,我们只需要用API即可管理内存。

②从语言角度来说,不同的语言提供了不同的操作内存的接口。

譬如汇编:根本没有任何内存管理,内存管理全靠程序员自己,汇编中操作内存时直接使用内存地址(譬如0xd0020010),非常麻烦;
譬如C语言:C语言中编译器帮我们管理直接内存地址,我们都是通过编译器提供的变量名等来访问内存的,操作系统下如果需要大块内存,可以通过API(malloc free)来访问系统内存。裸机程序中需要大块的内存需要自己来定义数组等来解决。
譬如C++语言:C++语言对内存的使用进一步封装。我们可以用new来创建对象(其实就是为对象分配内存),然后使用完了用delete来删除对象(其实就是释放内存)。所以C++语言对内存的管理比C要高级一些,容易一些。但是C++中内存的管理还是靠程序员自己来做。如果程序员new了一个对象,但是用完了忘记delete就会造成这个对象占用的内存不能释放,这就是内存泄漏。
Java/C#等语言:这些语言不直接操作内存,而是通过虚拟机来操作内存。这样虚拟机作为我们程序员的代理,来帮我们处理内存的释放工作。如果我的程序申请了内存,使用完成后忘记释放,则虚拟机会帮我释放掉这些内存。听起来似乎C# java等语言比C/C++有优势,但是其实他这个虚拟机回收内存是需要付出一定代价的,所以说语言没有好坏,只有适应不适应。当我们程序对性能非常在乎的时候(譬如操作系统内核)就会用C/C++语言;当我们对开发程序的速度非常在乎的时候,就会用Java/C#等语言。

2.内存中常见的相关的概念?

      ①内存:

从硬件角度:存实际上是电脑的一个配件(一般叫内存条)。根据不同的硬件实现原理还可以把内存分成SRAM和DRAM。

从逻辑角度:内存是这样一种东西,它可以随机访问、并且可以读写。

②位和字节:

内存单元的大小单位有4个:位(1bit) 字节(8bit) 半字(一般是16bit)  字(一般是32bit)
在所有的计算机、所有的机器中(不管是32位系统还是16位系统还是以后的64位系统),位永远都是1bit,字节永远都是8bit。

③内存位宽:

从硬件角度讲:硬件内存的实现本身是有宽度的,也就是说有些内存条就是8位的,而有些就是16位的。那么需要强调的是内存芯片之间是可以并联的,通过并联后即使8位的内存芯片也可以做出来16位或32位的硬件内存。
从逻辑角度讲:内存位宽在逻辑上是任意的,甚至逻辑上存在内存位宽是24位的内存(但是实际上这种硬件是买不到的,也没有实际意义)。从逻辑角度来讲不管内存位宽是多少,我就直接操作即可,对我的操作不构成影响。但是因为你的操作不是纯逻辑而是需要硬件去执行的,所以不能为所欲为,所以我们实际的很多操作都是受限于硬件的特性的。譬如24位的内存逻辑上和32位的内存没有任何区别,但实际硬件都是32位的,都要按照32位硬件的特性和限制来干活。

④内存编址方法:

内存在逻辑上就是一个一个的格子,这些格子可以用来装东西(里面装的东西就是内存中存储的数),每个格子有一个编号,这个编号就是内存地址,这个内存地址(一个数字)和这个格子的空间(实质是一个空间)是一一对应且永久绑定的。

内存编址是以字节为单位的

内存和数据类型的关系?数据类型必须和内存相匹配才能获得最好的性能,否则可能不工作或者效率低下。比如说,在32位系统中定义变量最好用int,因为这样效率高。原因就在于32位的系统本身配合内存等也是32位,这样的硬件配置天生适合定义32位的int类型变量,效率最高。也能定义8位的char类型变量或者16位的short类型变量,但是实际上访问效率不高。

3.内存对齐

内存的访问可以分为两种:对齐访问和非对齐访问。

非对齐访问:可以节约内存,但是效率低,这种方法在刚开始发展计算机是比较流行,但是在现在追求效率的时代,这个方法比较少用了。比如在内存中访问int数据,32位系统中,1 2 3 4或者 2 3 4 5 或者 3 4 5 6访问。

对齐访问:对齐访问很配合硬件,所以效率很高。比如:0 1 2 3

4.C语言如何操作内存

①直接使用内存:

int a; a = 5; a += 4;

int a; // 编译器帮我们申请了1个int类型的内存格子(长度是4字节,地址是确定的,但是只有编译器知道,我们是不知道的,也不需要知道。),并且把符号a和这个格子绑定。
a = 5; // 编译器发现我们要给a赋值,就会把这个值5丢到符号a绑定的那个内存格子中。
a += 4; // 编译器发现我们要给a加值,a += 4 等效于 a = a + 4;编译器会先把a原来的值读出来,然后给这个值加4,再把加之后的和写入a里面去。
(相当于读改写)

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

数据类型决定长度的含义:我们一个内存地址(0x30000000),本来这个地址只代表1个字节的长度,但是实际上我们可以通过给他一个类型(int),让他有了长度(4),这样这个代表内存地址的数字(0x30000000)就能表示从这个数字(0x30000000)开头的连续的n(4)个字节的内存格子了(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)。
数据类型决定解析方法的含义:譬如我有一个内存地址(0x30000000),我们可以通过给这个内存地址不同的类型来指定这个内存单元格子中二进制数的解析方法。譬如我 (int)0x30000000,含义就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)这4个字节连起来共同存储的是一个int型数据;那么我(float)0x30000000,含义就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)这4个字节连起来共同存储的是一个float型数据;

②用指针来间接访问内存:

C语言中的指针,全名叫指针变量,指针变量其实很普通变量没有任何区别。譬如int a和int *p其实没有任何区别,a和p都代表一个内存地址(譬如是0x20000000),但是这个内存地址(0x20000000)的长度和解析方法不同。a是int型所以a的长度是4字节,解析方法是按照int的规定来的;p是int *类型,所以长度是4字节,解析方法是int *的规定来的(0x20000000开头的连续4字节中存储了1个地址,这个地址所代表的内存单元中存放的是一个int类型的数)。

③用数组来管理内存:

数组管理内存和变量其实没有本质区别,只是符号的解析方法不同。int b[10];// 编译器分配40个字节长度给b,并且把首元素首地址和符号b绑定起来

优势:数组比较简单,访问用下标,可以随机访问。
缺陷:1 数组中所有元素类型必须相同;2 数组大小必须定义时给出,而且一旦确定不能再改。

④内存管理之结构体:

结构体就是为了解决数组的第一个缺陷的,即为了解决 数组中所有元素类型必须相同。结构体中的类型可以不一样。要知道,内核中就是利用这一点来实现结构体内嵌指针实现面向对象,即用C语言实现了面向对象。

5.C语言中几种数据结构

①内存管理之栈:

栈是一种数据结构,C语言中使用栈来保存局部变量。栈的特点是入口即出口,只有一个口,另一个口是堵死的。所以先进去的必须后出来,即先进后出。

栈主要是用来管理局部变量。

栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成。

栈的约束:首先,栈是有大小的。所以栈内存大小不好设置。如果太小怕溢出,太大怕浪费内存。(这个缺点有点像数组)其次,栈的溢出危害很大,一定要避免。所以我们在C语言中定义局部变量时不能定义太多或者太大(譬如不能定义局部变量时 int a[10000]; 使用递归来解决问题时一定要注意递归收敛)

②内存管理之堆:

堆这种内存管理方式特点就是自由(随时申请、释放;大小块随意)。堆内存是操作系统划归给堆管理器(操作系统中的一段代码,属于操作系统的内存管理单元)来管理的,然后向使用者(用户进程)提供API(malloc和free)来使用堆内存

堆管理内存的特点:特点一:容量不限(常规使用的需求容量都能满足)。特点二:申请及释放都需要手工进行,手工进行的含义就是需要程序员写代码明确进行申请malloc及释放free。如果程序员申请内存并使用后未释放,这段内存就丢失了(在堆管理器的记录中,这段内存仍然属于你这个进程,但是进程自己又以为这段内存已经不用了,再用的时候又会去申请新的内存块,这就叫吃内存),称为内存泄漏。在C/C++语言中,内存泄漏是最严重的程序bug,这也是别人认为Java/C#等语言比C/C++优秀的地方。

使用方法: void *malloc(size_t size); 堆内存申请时,调用malloc/calloc/realloc

void free(void *ptr); 堆内存释放时最简单,直接调用free释放即可。














0 0