当前位置: 首页 > news >正文

C语言自学--动态内存管理

目录

1、为什么要有动态内存分配

2、 malloc和free

2.1、malloc

2.2、free        C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

防止野指针

3、calloc和realloc

3.1、calloc

3.1.1、举例说明malloc和calloc的区别:

3.2、realloc

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

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

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

4.3 对非动态开辟内存使用free释放

4.4 使用free释放⼀块动态开辟内存的⼀部分

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

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

4.7、补充(realloc等价于malloc)

5、动态内存经典笔试题分析

5.1、题目1

5.2、题目2

5.3、题目3

5.4、题目4

6、柔性数组

6.1、柔性数组的特点:

6.2、柔性数组的使用

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

7.1、栈区(stack)

7.2、堆区(heap)

7.3、数据段(静态区,static)

7.4、代码段


1、为什么要有动态内存分配

        我们已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

     上述空间分配方式存在两个局限性:

  1. 分配的空间大小固定不变
  2. 数组声明时必须指定长度,且一旦确定就无法修改

     然而实际编程中,我们经常需要在程序运行时才能确定所需空间大小。这种需求使得编译时静态分配的方式显得力不从心。为此,C语言引入了动态内存管理机制,允许程序员在运行时自主申请和释放内存,大大提高了内存管理的灵活性。


2、 malloc和free

  • 2.1、malloc

        C语言提供了一个动态内存开辟的函数:

void* malloc (size_t size);

        该函数用于在内存中申请一块连续的可用空间,并返回指向该空间的指针。

特性说明:

  • 成功申请时:返回指向已分配空间的指针
  • 申请失败时:返回NULL指针
  • 返回值类型为void*,因此使用前需要进行类型转换
  • 当size参数为0时,malloc的行为标准是未定义的,函数行为取决于具体编译器实现
#include <stdlib.h>
int main()
{//申请10个整型的空间int* p = (int *)malloc(10*sizeof(int));if (p == NULL){//空间开辟失败perror("malloc");return 1;}//空间开辟成功,可以使用这40个字节int i = 0;for (i=0;i<10;i++){*(p + i) = i + 1;}return 0;
}


  • 2.2、free        C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
void free(void *ptr);

  free函数用于释放之前通过malloccallocrealloc动态分配的内存空间。释放后的内存会被系统回收,可供后续分配使用。

  • 若参数ptr指向的空间并非动态分配,则free函数的行为是未定义的。
  • 若参数ptrNULL指针,该函数不会执行任何操作。
    注意:mallocfree函数均声明在stdlib.h头文件中。

不妨思考:为何要执行 p = NULL 这一操作

#include <stdlib.h>
int main()
{//申请10个整型的空间int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){//空间开辟失败perror("malloc");return 1;}//空间开辟成功,可以使用这40个字节int i = 0;for (i = 0; i < 10; i++){*(p + i) = i + 1;}//释放free(p);p = NULL;return 0;
}

防止野指针

        在动态内存分配中,free(p) 释放了 p 指向的内存空间,但 p 本身仍保留原来的地址值。如果不将 p 设为 NULLp 会成为一个野指针(Dangling Pointer),指向已释放的内存区域。通俗来说,就是p指向的空间不属于当前程序,但还是找得到这个空间,这就是成为野指针了。


3、calloc和realloc

  • 3.1、calloc

        C语言还提供了一个名为calloc的函数,用于动态内存分配。其函数原型如下:

void* calloc (size_t num, size_t size);
  • 分配 num 个大小为 size 字节的内存空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

参数说明:

  • num:需要分配的元素数量(size_t 类型)
  • size:每个元素占用的字节数(size_t 类型)

返回值:

  • 成功时返回指向分配内存起始地址的 void* 指针。
  • 失败时返回 NULL(如内存不足)。
3.1.1、举例说明malloc和calloc的区别:
  • #include <stdio.h>
    int main()
    {//申请10个整型的空间int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){//空间开辟失败perror("malloc");return 1;}//空间开辟成功,可以使用这40个字节int i = 0;for (i = 0; i < 10; i++){printf("%d ",p[i]);//*(p+i)}//释放free(p);p = NULL;return 0;
    }

        可以看出,malloc对申请的地址没有初始化,都是随机值,我们再来看看calloc:

  • //calloc
    #include <stdlib.h>
    #include <stdio.h>
    int main()
    {//申请10个整型的空间int* p = (int*)calloc(10 , sizeof(int));if (p == NULL){//空间开辟失败perror("malloc");return 1;}//空间开辟成功,可以使用这40个字节int i = 0;for (i = 0; i < 10; i++){printf("%d ",p[i]);//*(p+i)}//释放free(p);p = NULL;return 0;
    }
    

        不难看出,calloc 会在返回地址之前把申请的空间的每个字节初始化为全0


  • 3.2、realloc
  • realloc函数的出现让动态内存管理更加灵活。
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那realloc 函数就可以做到对动态开辟内存大小的调整。 函数原型如下:
void* realloc (void* ptr, size_t size);

ptr 表示待调整的内存地址(一定要是动态调整的地址才行,不能随便传个地址!!
size 指定调整后的新内存大小
函数返回值为调整后的内存起始地址

该函数不仅会调整原有内存空间大小,还会将原内存中的数据迁移至新空间

realloc 在调整内存空间时存在两种情形:

  1. 原内存空间之后存在足够大的连续空间
  2. 原内存空间之后没有足够的连续空间

在内存扩展时存在两种不同情况:

  1. 情况1:若原有内存块后有足够空间,则直接追加扩展,原数据保持不变。
  2. 情况2:若后续空间不足,则需在堆中另寻足够大的连续空间,此时函数将返回新的内存地址。

鉴于这两种机制,使用realloc函数时需特别注意。

#include <stdlib.h>
#include <stdio.h>
int main()
{//申请10个整型的空间int* p = (int*)calloc(10, sizeof(int));if (p == NULL){//空间开辟失败perror("malloc");return 1;}//使用空间int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);//*(p+i)}//扩容,希望调整为20个字节的整型空间int *ptr = (int *)realloc(p,20*sizeof(int));if (ptr != NULL){p = ptr;//如果修改成功,那么指针起始地址就和原来一样,//反之,修改失败上面返回的就是空指针给ptr,// 然后单独复制一份到堆区域,并释放掉原来申请的空间,所以起始地址就和原来不一样}//使用// ...//释放free(p);p = NULL;return 0;
}

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

  • 4.1 、对NULL指针的解引用操作
  • #include <stdio.h>int main()
    {int*p = (int *)malloc(10 * sizeof(int));//if (p ==NULL)//{//perror("malloc");//return 1;//}int i = 0;for (i = 0; i < 10; i++){p[i] = i;//*(p+i)//如果p的值是NULL,就会有问题}free(p);p = NULL;return 0;
    }
    
  • 4.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);
    }
  • 4.3 对非动态开辟内存使用free释放
    • void test()
      {int a = 10;int* p = &a;free(p);//ok?
      }

        free()函数只能用于释放通过malloccallocrealloc等动态内存分配函数分配的堆内存。而变量a是局部变量,其内存是在栈上自动分配的,不属于堆内存范畴。

  • 4.4 使用free释放⼀块动态开辟内存的⼀部分
  • void test()
    {int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
    }
    
  • 4.5 对同⼀块动态内存多次释放
  • void test()
    {int *p = (int *)malloc(100);free(p);free(p);//重复释放
    }
  • 4.6 动态开辟内存忘记释放(内存泄漏)
  • void test()
    {int *p = (int *)malloc(100);if(NULL != p)
    {*p = 20;
    }
    }
    int main()
    {test();while(1);
    }
    •         忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。 切记:动态开辟的空间⼀定要释放,并且正确释放。
4.7、补充(realloc等价于malloc)

        当 realloc 的第一个参数为 NULL 时,其行为与 malloc 完全一致

  • int main()
    {//正常用法//int* p = (int*)malloc(20);//realloc(p, 40);//malloc==reallocint* p = (int*)realloc(NULL, 40);//==malloc(40)if (p == NULL){//....return 1;}free(p);p = NULL;return 0;
    }

5、动态内存经典笔试题分析

  • 5.1、题目1
  • void GetMemory(char *p){p = (char *)malloc(100);}void Test(void){char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);}

        运行Test 函数会有什么样的结果?

  • 问题分析

        运行 Test 函数会导致程序崩溃或未定义行为。以下是具体原因和过程:

  • 原因1:指针传递问题

  GetMemory 函数的参数 p 是一个指针,但传递方式是值传递。当 str 作为参数传入时,pstr 的副本,而非 str 本身。因此,malloc 分配的内存地址赋值给 p 后,str 仍然为 NULL

  • 原因2:解引用空指针

  strcpy(str, "hello world") 尝试向 str(仍为 NULL)写入数据,导致解引用空指针,触发段错误(Segmentation Fault)或访问违规。

  • 修正方法

  • 方法1:传递指针的指针

void GetMemory(char **p) {*p = (char *)malloc(100);
}
void Test() {char *str = NULL;GetMemory(&str); // 传递str的地址strcpy(str, "hello world");printf("%s", str);free(str); // 释放内存
}
  • 方法2:返回动态分配的内存

char* GetMemory() {return (char *)malloc(100);
}
void Test() {char *str = GetMemory();strcpy(str, "hello world");printf("%s", str);//printf(ptr);//打印字符串相当于传字符串的首地址==printf("hehe\n");free(str);
}

注意事项

  • 动态分配的内存需手动释放,避免内存泄漏。
  • 直接操作未初始化的指针或空指针是危险行为,必须确保指针有效后再使用。

  • 5.2、题目2
 char *GetMemory(void){char p[] = "hello world";return p;}
void Test(void){char *str = NULL;str = GetMemory();printf(str);}

  GetMemory()函数中定义的数组p是局部变量,存储在栈内存中。当函数返回时,栈帧被销毁,该数组占用的内存被系统回收。虽然返回了数组首地址p,但该地址指向的内存内容已不再有效。

  Test()函数中将返回的地址赋值给str并尝试打印,此时访问的是已被释放的栈内存。这种操作属于未定义行为(Undefined Behavior),可能导致以下几种结果:

  1. 程序崩溃(最常见情况)
  2. 输出乱码或错误内容
  3. 看似正常输出"hello world"(残留数据未覆盖)
  4. 其他不可预测行为
  • 使用静态存储
char* GetMemory(void) {
//void 表示该函数不接受任何参数。这是一种明确的写法,表明调用该函数时不需要传入任何参数。static char p[] = "hello world";return p;
}
  • 动态内存分配

char* GetMemory(void) {char* p = malloc(12);strcpy(p, "hello world");return p;
}
// 需记得在调用后free 
  • 传入缓冲区
void GetMemory(char* buf, size_t size) {strncpy(buf, "hello world", size);
} 
  • 5.3、题目3
    • void GetMemory(char **p, int num)
      {*p = (char *)malloc(num);
      }
      void Test(void)
      {char *str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
      }

              原代码能正确运行并输出"hello",但存在内存泄漏和未检查分配失败的风险。分配的内存未被释放,导致内存泄漏。虽然程序能正确运行输出,但每次调用Test都会泄露100字节内存。

  • void Test(void)
    {char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);free(str);str = NULL;
    }
  • 5.4、题目4
  • #include <stdio.h>
    #include <string.h>
    void Test(void)
    {char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
    }int main()
    {Test();return 0;
    }

    Test函数中存在以下几个关键问题:

  • 内存释放后未置空指针free(str)后,str指针仍然指向原来的内存地址,但该内存已被释放,属于悬空指针(dangling pointer)。
  • 释放后访问内存if (str != NULL)的条件判断无效,因为free不会自动将指针置为NULL,此时str仍为非NULL的悬空指针。
  • 释放后写入内存strcpy(str, "world")会尝试向已释放的内存写入数据,属于未定义行为(Undefined Behavior, UB)。
  • free(str);
    str = NULL; // 避免悬空指针
    if (str != NULL) // 此时条件为假,不会进入分支 
    


6、柔性数组

        柔性数组(Flexible Array Member)是C99标准引入的特性,允许结构体的最后一个成员是未指定大小的数组。这种数组不占用结构体本身的内存空间,仅为动态分配内存提供便利的访问方式。

struct S
{int n;char c;double d;int arr[];//未知大小的数组,arr就是柔性数组的成员
};//有些编译器会报错⽆法编译可以改成:
struct S
{int n;char c;double d;int arr[0];//未知大小的数组,arr就是柔性数组的成员
};
  • 6.1、柔性数组的特点:
  • 结构中的柔性数组成员前⾯必须至少⼀个其他成员。
  • sizeof返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
  • #include <stdio.h>
    struct S
    {int n;//4int arr[];
    };int main()
    {printf("%d",sizeof(struct S));return 0;
    }
  • #include <stdio.h>
    struct S
    {int n;//4int arr[];
    };int main()
    {//malloc(sizeof(struct S) + 20*sizeof(int));//			4字节			80字节,为了适应柔性数组//该怎么接收malloc的返回值呢,用结构体指针struct S *ps = (struct S *)malloc(sizeof(struct S) + 20 * sizeof(int));return 0;
    }
  • 6.2、柔性数组的使用
  • #include <stdio.h>
    #include <stdlib.h>
    struct S
    {int n;//4int arr[];
    };int main()
    {//malloc(sizeof(struct S) + 20*sizeof(int));//			4字节			80字节,为了适应柔性数组//该怎么接收malloc的返回值呢,用结构体指针struct S *ps = (struct S *)malloc(sizeof(struct S) + 20 * sizeof(int));if (ps == NULL){perror("malloc");return 1;}//使用这些空间ps->n = 100;int i = 0;for (i = 0; i < 20; i++){ps->arr[i] = i + 1;}//调整ps指向空间的大小struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40 * sizeof(int));if(ptr !=NULL){ps = ptr;//ps指向新追加的空间}else{return 1;//调整失败,结束下来}for (i = 0; i < 20; i++){ps->arr[i] = i + 1;}//使用for (i = 0; i < 40; i++){printf("%d\n", ps->arr[i]);//前20个是1-20,后20个是随机值}//释放空间free(ps);ps = NULL;return 0;
    }

柔性数组的优势

  • 内存连续:结构体和数组成员在内存中连续分布,减少内存碎片,提升访问效率。
  • 简化释放:只需一次free即可释放全部内存,避免指针嵌套导致的多次释放。
  • 节省空间:相比指针成员+动态数组的方式,省去了指针本身的存储开销。

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

  • 7.1、栈区(stack)

在函数执行时,局部变量、函数参数、返回数据、返回地址等存储在栈区。栈区的内存分配和释放由编译器自动完成,效率高但容量有限。函数执行结束时,栈上的存储单元自动释放。

  • 7.2、堆区(heap)

由程序员手动分配和释放,如使用malloccallocnew等动态分配内存。若未释放,程序结束时可能由操作系统回收。堆区分配方式类似链表,容量较大但管理复杂。

  • 7.3、数据段(静态区,static)

存放全局变量和静态变量(如static修饰的局部变量或全局变量)。程序结束后由系统自动释放,生命周期贯穿整个程序运行期间。

  • 7.4、代码段

存储函数体(包括类成员函数和全局函数)的二进制代码,通常是只读区域,防止程序意外修改指令。

http://www.dtcms.com/a/450988.html

相关文章:

  • 重庆人居建设集团网站网络编程有哪些
  • 滨州网站建设九鲁体育西网站开发方案
  • 成都网站建设 3e网站建设wordpress seo 优化插件
  • 汽油价格网长沙网站优化排名推广
  • 四川城乡建设厅官方网站关于营销的网站有哪些
  • 山东大源建设集团网站wordpress cn
  • 外贸服饰网站建设网络教室网站建设
  • 算法学习 || 动态规划(买卖股票的最佳时机)
  • mRemoteNG下载安装配置教程(附安装包)
  • 山东网站营销推广费用网站电话改了子页怎么改
  • 做电器哪个网站好保定seo排名
  • I.MX8QM创建wic镜像文件
  • 做塑料的网站名字ui网页界面设计素材
  • 哪一款软件可以自己做网站免费申请自己的网站
  • 显示英文以及字符
  • 邯郸网站建设怎么做手机访问自动跳转到wap网站的代码
  • 网站备案知识做网站界面设计注意什么
  • 专业的饰品行业网站开发网站建设推广销售人员
  • 沈阳哪家网站制作公司比较好云南建设厅查证网站
  • Memcached stats sizes 命令详解
  • 大连网站制作案例口碑营销ppt
  • 网站建设网页制作软件有哪些教育行业网站建设价格
  • 算法竞赛常见bug或错误
  • ps做网站要求高吗c 怎么做能让窗体访问网站
  • 网站怎么做排名呢如何免费找精准客户
  • 怎么看一个网站是不是织梦网站建设的外国文献
  • 钢城网站建设百度推广和优化哪个好
  • 【操作系统基础】线程
  • 有哪些高端的网站教师可以做网站吗
  • 做网站商城的目的是什么网络服务协议模板