动态内存管理总结

简介: 动态内存管理总结

1.前言:

对于很多参加竞赛或者平时在线OJ的同学来说,当大家使用C语言来书写代码的时候经常会遇到这样的情况:

题目要求数组的长度可变,并且要求输入一个整型当做数组的长度,我第一次遇到这个要求的时候是这样处理的:

int a=0;

scanf(“%d”,&a);

int arr1[a]={0};

正当我信心满满的提交答案时,发现编译器这样写时不通过的,但是从语法上来看没错啊?而且C99之后也支持C语言的变长数组的概念,但我们毕竟不是OJ的创始人,过不去的时候不能硬着头皮非要一条路走到黑,所以我们要另辟蹊径,这个时候,我们就需要动态内存管理来创建一个控制长度的数组了,所以,接下来我将说一说何为动态内存管理。

2.关于动态内存的概念理解:

首先我们先来理解一张图:

当我们写下一个程序的时候,其本质实际上是直接划分内存里面的空间,将空间分成有规律的几块,其具体要求如下:

  1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时
    这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内
    存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。

我们平时开辟的整型,浮点型,结构体型甚至是数组开辟好之后都是在栈区直接开辟的,但我们今天要说的动态内存是在堆区开辟的,堆区正如上面所说,是专门给程序员开辟和释放的灵活可变的空间。

3.动态内存使用的基本方式和函数介绍:

1.基本函数:malloc calloc realloc free

就像文件操作一样,动态内存也强调其使用的步骤,其最关键的在于使用了动态内存后一定要及时释放,即使用free函数。

1.free函数:

其基本格式如下:free函数是用来释放掉我们所开辟的内存的

void free (void ptr);*

注意!!:free只是释放内存,但它不会将ptr指针自动放置成空指针,在指针章节我们说过,不及时置空指针,变成野指针是很麻烦的,所以我们使用完free后一定要自己手动置空指针,常见的方式为:

free(str1);
str1=NULL;

2.malloc函数:(引用头文件#include<stdlib.h>)

malloc函数即最基础的动态开辟的函数,它的作用是在堆区申请一块空间并让程序员使用,基本格式如下:

void malloc (size_t size);*

它的参数是要申请的堆区空间的大小,返回值是申请的这块动态内存的指针,我们注意到为void*类型的,对于这种类型的接收,我们只需要按我们所需的强转其指针即可。如同文件操作一样,使用动态开辟的函数使用后同样也要检验,其基本格式如下为例:

int*arr1=(int*)malloc(10*sizeof(int));
if(arr1==NULL)
{
    perror(:malloc failed");
    exit(-1);/return 1;
}

这便是我动态开辟的一个10个元素的数组,对于开辟失败的返回值,使用eixt(-1)和return 1都可以,其目的主要是跟return 0区分开,让程序员知道程序到底是因为什么结束的。后面的calloc realloc都遵循这个检验的步骤。

3.calloc函数:(#include<stdlib.h>)

calloc函数的用法与malloc大致相同,唯一不同的是它不仅开辟空间,还会将开辟的空间的每一个元素设置为0,基本格式如下:

void calloc (size_t num, size_t size);*

calloc的第一个参数为要开辟空间的元素个数,第二个为每一个元素的大小,所以calloc在开辟动态数组方面更加方便,但似乎不适合用来开辟结构体,它的返回情况和malloc一样。检验步骤同理。

4.realloc函数:(#include<stdlib.h>)

realloc的作用为调整由malloc和calloc开辟的空间的大小,使得其在调整动态内存的大小方面可以起到自动调整的作用,这对于使用顺序表以至于构建通讯录都是很方便的,格式如下:

void* realloc (void* ptr, size_t size);

它的第一个参数为用malloc realloc开辟的动态内存的地址,第二个参数为要调整的动态内存的大小**(注意这里不是累加的调整,是设定为多大就调整为多大)**,它的返回值为开辟的这块空间的地址,注意,我没有说返回值一定是原来开辟的地址,而是调整后的地址,这是由于堆区的开辟本身为碎片化的而不是有顺序的进行,realloc的调整倘若不影响其他动态空间,它返回的是原地址,但假如开辟的空间过大与其他冲突了,realloc就会重新找到一块地方开辟,同时将原来的那一块空间释放掉,这个时候它传过来的指针就是新地址的指针了。所以我在这里强调,一定不要把这个返回值单纯的看作一个源地址的指针,你就理解为这个空间的地址指针,你用之前的动态开辟的指针接收也就可以,用新的指针接收也可以。

realloc的特殊用法:当realloc对应的指针参数没有开辟动态空间的时候,此时使用realloc,它的用法跟malloc一样,直接为这个指针开辟一个对应的空间,并且返回这块的地址。

如下:

int*arr1=(int*)realloc(NULL,10*sizeof(int));//此时,realloc的作用与malloc作用相同
if(arr1==NULL)
{
    perror("realloc failed");
    exit(-1);/return 1;
}

那么,我们动态内存开辟的基本格式是什么呢?

void*arr1=(void*)malloc()/ realloc()/ calloc();//开辟内存
检验过程
{
}
使用过程
{
}
free(arr1);//内存释放过程
arr1=NULL;

使用动态内存开辟的时候,这几个步骤至关重要不能省略。

4.动态内存使用的常见错误总结:

1.对NULL指针的解引⽤操作

例如:

void test()
 {
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
 }

2.对动态开辟空间的越界访问

例如:

void test()
 {
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 free(p);
 }

3.对⾮动态开辟内存使⽤free释放

例如:

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//ok?
 }

4.使⽤free释放⼀块动态开辟内存的⼀部分

例如:

void test()
 {
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
 }

5.对同⼀块动态内存多次释放

例如:

void test()
 {
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
 }

6.动态开辟内存忘记释放(内存泄漏):忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。

切记:动态开辟的空间⼀定要释放,并且正确释放。!!!!!!!且要手动置空

例如:

void test()
 {
     int *p = (int *)malloc(100);
     if(NULL != p)
     {
         *p = 20;
     }
 }
 int main()
 {
    test();
    while(1);
 }

5.柔性数组:

回到前言中的问题,我们现在已经知道,利用动态开辟是可以操控数组长度的,但是C99中提到了这样一个概念,柔性数组,即在一个结构体内部包含多个成员变量,但最后一个成员是一个大小未知的数组,这就是柔性数组。

例如:

c
typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;
有时候在a的中括号里面写0也是可以的,但我本人建议按照定义来不写大小比较好理解。

1.柔性数组的特点:

• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。

**• sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。**你可以理解为它实际上是附加在结构体内部的一种特殊的结构,虽然在结构体里但开辟的空间是独立的

• 包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤

⼩,以适应柔性数组的预期⼤⼩。

2.柔性数组的使用:

柔性数组同样也是利用动态内存函数来开辟的,但特殊的是,它的大小是要独立于动态内存开辟的结构体而开辟的,具体如下:

type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));

如上图,我们独立于结构体之外单独为柔性数组开辟100个元素的空间,而不是算在一起开辟,这样开辟有利于我们理清结构体和柔性数组的大小。

倘若不用柔性数组,正常开辟也可以,如下:

type_a *p = (type_a *)malloc(sizeof(type_a));
 p->i = 100;
 p->p_a = (int *)malloc(p->i*sizeof(int));

所以我们说说柔性数组的好处:

1.第⼀个好处是:⽅便内存释放

如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤

⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能

指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返

回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。通俗来讲就是,简单清晰,干净利落。

2.第⼆个好处是:这样有利于访问速度.

连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你

跑不了要⽤做偏移量的加法来寻址)。注意,堆区大量使用动态开辟并不是一件好事,这样会导致堆区的空间碎片化,大量空间无法使用。

6.总结:

以上就是动态内存开辟的全部内容的,C语言之所以不适合竞赛,就在于它不能像C++那样使用vector调整长度,只能动态开辟数组来使用,但是动态开辟依旧是一个十分好用的操作,对于数据结构的链表和顺序表的节点创建和大小调整很重要,所以还是建议大家能熟练掌握动态内存开辟的内容。

目录
相关文章
|
1月前
|
编译器 程序员 C语言
动态内存管理(超详细!)
动态内存管理(超详细!)
26 2
|
1月前
|
程序员 C语言 C++
详解动态内存管理!
详解动态内存管理!
|
5月前
|
C语言 Python
动态内存管理(下)
动态内存管理(下)
29 0
|
2月前
|
安全 C++ 开发者
c++动态内存管理(二)
c++动态内存管理(二)
87 0
|
4月前
|
编译器
动态内存管理(1)
动态内存管理(1)
35 0
|
4月前
|
程序员 编译器 C语言
动态内存管理(2)
动态内存管理(2)
35 0
|
4月前
|
程序员 编译器
动态内存管理-1
动态内存管理
32 0
|
4月前
|
程序员 C语言 C++
动态内存管理-2
动态内存管理
20 0
|
5月前
|
C语言 C++
C++中的动态内存管理
C++中的动态内存管理
|
5月前
动态内存管理(下)
动态内存管理(下)
18 0