首页 > 系统相关 >C语言动态内存管理的讲解

C语言动态内存管理的讲解

时间:2024-11-27 17:29:27浏览次数:7  
标签:malloc 函数 int free C语言 内存 动态内存 讲解 空间

一、动态内存为何存在

在动态内存管理之前,我们已经学过了在栈空间开辟内存的方式:

int a = 4;(在栈区开辟四个字节的空间)

char arr[ 10 ];(在栈区开辟10个字节的连续空间)

这些开辟空间的方式有两个特点:

(1)空间开辟的大小是固定的;

(2)数组在申明的时候,必须指定数组的长度,数组空间大小一旦确定后续就不能调整了。

但是我们平常难免会面对这种情况,就是需要开辟多大的空间是程序开始运行时才知道的,那常量开辟空间的方式就不适用了,变量和数组的方式还不够灵活。因此C语言引入了动态内存开辟,使用动态内存分配可以自己来维护内存的使用生命周期,让程序员可以自己申请想要的空间,以及不用这部分空间后及时释放。这种动态内存开辟就相对来说非常灵活了。

本期就主要介绍动态内存开辟的方法以及注意事项。

二、malloc和free函数

malloc和free函数的使用都需要包含头文件<stdlib.h>

1、malloc函数

void* malloc(size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

(1)如果开辟成功,则返回一个指向开辟好的空间的指针
(2)如果开辟失败,则返回一个 NULL 指针,因此 malloc 的返回值一定要做检查
(3)返回值的类型是 void*,所以 malloc 函数并不知道要开辟的空间的类型,具体类型在使用的时候由使用者自己来决定;
(4)如果参数 size 为 0,malloc 的行为是标准是未定义的,取决于编译器。

2、free函数

free函数是专门用来做动态内存的释放和回收的。

void free ( void* ptr ) ;

(1)free函数的参数部分要传递的是要释放的空间的起始地址

(2)如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的
(3)如果参数 ptr 是 NULL 指针,则函数什么事都不做

程序退出的时候,即使没有free,操作系统也会主动回收这块内存空间,最坏的情况是:程序不退出,动态开辟的空间也不free,申请到的内存又不实用。

三、calloc和realloc函数

calloc和realllc函数的使用也需要包含头文件<stdlib.h>。

1、calloc函数

void* calloc (size_t num, size_t size);

(1)函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为 0
(2)与 malloc 函数的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0。

2、realloc函数

realloc函数的优势:realloc 函数的出现让动态内存管理更加灵活。有时我们会发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小进行调整。

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

(1)ptr 要调整的内存地址
(2)size 调整之后的新大小
(3)返回值调整之后的内存起始位置
(4)这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
realloc 在调整内存空间的是存在两种情况:

【1】原有空间之后有足够大的空间

当是情况【1】的时候,要扩展内存就直接在原有内存之后追加空间,原来空间的数据不发生变化。
【2】原有空间之后没有足够大的空间

当是情况 【2】 的时候,原有空间之后没有足够多的空间,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址,原来内存中的数据也会移动到新的空间中。

注:如果realloc调整内存空间失败,就会返回NULL。

四、常见的动态内存的错误

1、对NULL指针的解引用操作

void test()
{
    int* p = (int*)malloc(INT_MAX / 4);
    *p = 20;    
    free(p);
}

如果p的值是NULL,就发生了对NULL(空)指针的解引用,就会出现问题。

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;   
    }     
    free(p); 
}

当i==10时,会发生越界访问,导致错误。

3、对非动态开辟内存使用free

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

这里的p指向的内存空间不是动态开辟的,如果对p使用free,就是对非动态开辟的内存使用free,会导致错误出现。

4、使用free释放一块动态开辟内存的一部分

void test()
{
    int* p = (int*)malloc(100);
    p++;
    free(p);
}

p++后,p所代表的地址不是动态开辟的空间的首地址,p不再指向动态内存的起始位置,对p使用free会出现问题。

5、对同一块动态内存多次释放

void test()
{
    int* p = (int*)malloc(100);
    free(p);
    free(p);
}

这里对p多次使用free,重复释放,会导致问题出现。

6、动态开辟内存忘记释放(内存泄漏)

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

int main()
{
    test();
    while (1);
}
这里忘记释放不再使用的动态开辟的空间,会造成内存泄露。动态开辟的空间一定要释放,并且要正确释放。

五、柔性数组

C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。

1、柔性数组的特点

(1)结构中的柔性数组成员前面必须至少有一个其他成员
(2)sizeof 返回的这种结构大小不包括柔性数组的内存
(3)包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

2、柔性数组的使用

typedef struct st_type
{
    int i;
    int a[0];
}type_a;


int main()
{
     int i = 0;
    type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
    p->i = 100;
    for (i = 0; i < 100; i++)
    {
        p->a[i] = i;
    }
    free(p);
    return 0;
}
经过上述代码操作之后,柔性数组成员a,相当于获得了100个整型元素的连续空间。

3、柔性数组的优势

先介绍一种不使用柔性数组也能达到同样效果的方案:

typedef struct st_type
{
    int i;
    int* p_a;
}type_a;
int main()
{
    type_a* p = (type_a*)malloc(sizeof(type_a));
    p->i = 100;
    p->p_a = (int*)malloc(p->i * sizeof(int));
    for (int i = 0; i < 100; i++)
    {
        p->p_a[i] = i;
    }
    free(p->p_a);
    p->p_a = NULL;
    free(p);
    p = NULL;
    return 0;
}
那与使用柔性数组的方案进行对比,不难得出使用柔性数组的优势:

(1)方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用 free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次 free 就可以把所有的内存给释放掉。

(2)这样有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片。

六、总结C/C++中程序内存区域划分

C语言程序内存分配的几个区域:

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

标签:malloc,函数,int,free,C语言,内存,动态内存,讲解,空间
From: https://blog.csdn.net/2401_86861045/article/details/143978720

相关文章

  • 【计算机毕业设计选题推荐】基于springboot的某学院兼职平台的设计与实现 【附源码+讲
    ✍✍计算机编程指导师⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流!⚡⚡Java实战|SpringBoot/SSMPython实战项目|Django微信小程......
  • C语言:基本数据类型
    整型数据        用来存放整型数据的变量我们定义一个整型的变量,C编译系统实际是在内存中分配了能够存储一个整型数据的存储空间,并用变量名来标识这个空间,对该空间的使用也就可以通过变量名来访问。若不知道你所用的C编译系统对变量分配的存储空间的大小,可用sizeof......
  • C语言-数组
    数组的创建数组结构式数组类型数组名称数组大小=内容列如charArr[3]={1,2,3}intArr[3]={1,2,3}注意:在C99之前,【】中需要一个常量才可以数组的初始化初始化就是给它赋值,初始化分为完全初始化和非完全初始化完全初始化:将值全部装完非完全初始化:将值只装一部分一......
  • Spring:Spring事务管理讲解
    事务作用:在数据层保障一系列的数据库操作同成功同失败Spring事务作用:在数据层或**业务层**保障一系列的数据库操作同成功同失败1,事务介绍Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManagercommit是用来提交事务,rollback是用来回滚事务。PlatformT......
  • C语言实例之9斐波那契数列实现
    1.斐波那契数列简介斐波那契数列(Fibonaccisequence),又称黄金分割数列,因数学家莱昂纳多・斐波那契(LeonardoFibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”。它的特点是从第三项开始,每一项都等于前两项之和,数列的前两项通常定义为0和1(也有从1和1开始的定义......
  • C语言实例之10求0-200内的素数
    1.素数素数(Primenumber),也叫质数,是指在大于1的自然数中,除了1和它自身外,不能被其他自然数整除的数。例如2、3、5、7、11等都是素数,而4能被2整除、6能被2和3整除,所以它们不是素数。2.素数的特性与判断思路素数是指在大于1的自然数中,除了1和它自身外,不......
  • C语言——数组逐元素操作练习
            定义一个能容纳10个元素的整形数组a,从键盘读取9个整数存放到前9个数组元素中。一.        从键盘读取一个整数n和位置p(0<=p<=8),插入n到数组a中,插入位置:下标p。要求插入点及后续的数组元素都要后移动。    代码如下:intmain(){ intarr......
  • C语言函数递归经典题型——汉诺塔问题
    一.汉诺塔问题介绍        Hanoi(汉诺)塔问题。古代有一个梵塔,塔内有3个座A、B、C,开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上。有一个老和尚想把这64个盘子从A座移到C座,但规定每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程......
  • C语言(七)----指针(下)
    深入理解指针(4)字符指针变量#include<stdio.h>intmain(){ charch='h'; char*pch=&ch; printf("%c\n",*pch); *pch='g'; printf("%c\n",*pch); return0;}//结果为://h//g//也可以:#include<stdio.h>int......
  • 学习分享-队列-1(数据结构C语言)
    本章写的是基于链表的队列,通过链表来实现队列的操作一个基于链表的队列(Queue)数据结构,先进先出结构体定义typedefstructNode{intdata;structNode*next;structNode*pre;}Node;定义一个节点(Node)结构体,包含数据(data)、指向下一个节点的指针(next)和指向......