随笔:动态内存分配

在CPU中,CPU的职责就是执行运算而已,数据的来源则来自于内存区。但是由于CPU运算速度远远高于内存的读写速度,为了平衡这种速度差,CPU中引入了缓存机制。但是,CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度。因此CPU就加入了CPU registers(寄存器)这样一个东西。它通常被称为“零级缓存”。CPU在高速运算下,会优先读写寄存器,再由寄存器跟内存交换数据。按照存储介质速度的不同,下图把它们简单做了一个排列,它们的特点是:速度越快内存越小,速度越慢内存越大。

接下来,再来分别简单介绍一下这些存储介质

1.寄存器:我们常常看到 32位 CPU、64位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4个字节。

2.缓存:缓存就是速度比RAM更快的RAM。具体可以参考百度百科

3.RAM: 随机存取存储器。分为静态存储器SRAM和动态存储器DRAM,通常静态存储器速度更快,但是存储效率不如动态存储器,而且价格更高。如需了解更多,具体可以参考百度百科

4.磁盘:这个用过电脑的应该都比较熟悉,磁盘分为固态硬盘和机械硬盘。机械硬盘读写速度相对较慢,不过价格比较实惠。

现在来介绍一下内存空间:

一个内存空间里面包含了四个区域:代码区,全局变量和静态变量区,局部变量区(即栈区)以及堆区。

代码区和全局变量和静态变量区就和它们的名字一样,存储的就是代码和全局变量和静态变量。

下面重点介绍一下栈区和堆区:

一个程序在运行之前,每一个代码源文件会被编译器编译为可执行文件,在编译时期编译器则会预先在内存区分配所需静态内存,这些静态内存一般被指定上面提到的前三个区域。那么是什么样的变量会存储在栈里面呢?

基本数据类型都是存储在栈中的(当然前提是它不是全局的,下面默认都不是全局的),基础类型包括byte,char,short,int,float,double,long等,而数组类型要么是在声明期就指定它的大小,这时数组的内容都是存储在栈中的。还有一种情况是无法在编译期确定数组的实际大小,而是需要在程序运行时再动态分配内存,这时数组的内容就会存储在堆中了。不过又一点需要注意的是:虽然这个数组时存在堆中,但是数组变量是存储在栈中的,比如

int a[10] = malloc(sizeof(Int) * 10);

这个a变量是一个指针,存储的是堆中动态分配内容的首地址。a变量是存储在栈中的,和所有指针类型一样,它所占用的字节数是4个字节。值得一提的是,数组在堆中的内存分配是连续的,因此程序可以很轻松地通过(首地址+偏移量*下标)的方式得以访问到数组内的任何一个元素。在C语言中,动态分配的内存需要使用free函数手动释放掉。指向堆内存的指针变量会自动被释放掉。除了malloc函数,还有calloc或realloc函数也能动态分配内存。它们的区别是:

malloc函数的原型是void *malloc (unsigned int size) ,其作用是在内存的动态存储区中分配一个长度为size的连续空间。不过只是分配而已,并不会对所分配的内存做初始化操作。分配成功的话会返回分配内存的首地址,实际使用时需要做类型强转。如果分配内存失败,比如内存空间不足,那么就会返回一个NULL指针。所以在使用该函数返回的值时,需要先判断一下内存是否分配成功。

calloc函数的原型是void *calloc(size_t n, size_t size),其作用和malloc函数差不多,区别在于它比malloc函数多做了一个初始化所分配内存区域的步骤,会全部初始化为0。 这当然会造成额外的开销,所以如果不需要初始化的话,可以只使用malloc函数即可。

realloc函数的原型是extern void *realloc(void *mem_address, unsigned int newsize),其作用是动态改变已分配内存的大小,第一个参数是原始分配的内存首地址,第二个参数是需要分配的内存大小。比如用malloc分配了四个int大小的数据,后来因为某些原因需要调整该区域内存大小。就可以使用realloc函数。比如

int *a = malloc(4*sizeof(int));
int *b = realloc(a, 3*sizeof(int)); // 缩小
int *c = realloc(a, 5*sizeof(int)); // 扩大

realloc函数的第一个参数必须是来自于malloc或者calloc或者realloc函数的。realloc函数的第一个参数如果传入的是NULL,那么它就和malloc函数作用一样了。realloc函数如果是缩小内存的话,返回的指针一般还是原来的指针,只不过缩小之后的内容,被剔除的内存被系统回收了,如果再访问,可能会引起野指针异常。而如果realloc函数是扩大内存的话,它所返回的指针就不一定是原来的指针内容了。因为内存分配的都是连续的地址空间,如果在当前地址下分配不了指定的size大小,则系统会另外开辟一段内存空间,然后将之前指针指向的内容复制到新的内存地址,并自动释放掉原来的指针,最后返回的是新的内存地址。如果因为某些原因,比如系统内存不足,那么该函数将会返回一个NULL指针。所以它在使用之前也需要做判空。

总结一下:使用动态分配的内存都是使用malloc函数、calloc函数或者realloc函数分配的,它们都存储在堆中,而指向它们的指针变量是存在栈中。动态分配的内存可能会分配失败,所以需要做判空操作。动态分配的内存必须使用free()函数释放,否则会造成内存泄漏。

参考链接:百度百科--动态内存分配Java静态内存和动态内存解析汇编语言入门教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章