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

C语言学习之动态内存的管理

        学完前面的C语言内容后,我们之前给内存开辟空间的方式是这样的。

int val=20;
char arr[10]={0};

         我们发现这个方式有两个弊端:空间是固定的;同时在声明的时候必须指定数组的长度,一旦确定了大小就不能调整的。

        而实际应用的过程中,我们发现定长的数组往往是不能满足需要的。因此我们需要对内存进行动态化的处理。

目录

malloc函数

free函数

calloc函数

realloc函数

动态内存管理的几个常见错误

对空指针解引用

对动态开辟内存的越界访问

对非动态内存使用free函数

 使用free函数释放了一部分

同一动态内存多次释放

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

一些经典的内存方面的例题:

1.

2.

3.

4.

柔性数组

柔性数组的特点

        柔性数组的使用

        柔性数组的优势

C/C++中内存区域划分


内存三大区域主要存储的数据类型。

malloc函数

        malloc是C语言动态内存开辟的一个函数,它的语法形式是这样的

void * malloc(size_t size)

         其中size是指定的大小(字节)

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

        如果开辟成功则返回一个指向开辟好空间的指针则返回一个指向开辟好空间的指针。;如果开辟失败则返回一个NULL指针,因此一定要对malloc返回值做检查。

        返回值类型为void*,所以malloc函数并不知道开辟空间的类型,具体使用的时候使用者自己决定。

        如果参数size的数值为0,则malloc的行为标准是未定义,具体行为取决于编译器。

        使用该函数前需要包含头文件<stdlib.h>

        但是当我们申请空间调用后一定要销毁内存空间,因此我们还需要free函数

free函数

        free函数专门用来做动态内存的释放和回收的函数。语法结构如下:

void *free(void *ptr)

        ptr中存放的是要释放的空间的起始位置。

        如果ptr指向的内存空间不是动态的,free行为未定义;如果ptr指向的内存是NULL指针,则函数什么都不做。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{int *p=(int*)malloc(sizeof(int)*10);if (p == NULL){perror("空间申请失败");return 1;//异常返回,退出程序}//使用内存for (int i = 0; i < 10; i++){p[i] = i;}for (int i = 0; i < 10; i++){printf("%d ", p[i]);}//释放内存free(p);//如果这里不写free函数则程序运行时候系统自动回收这些内存。//但是可能导致内存泄漏//同时这么写是很危险的,因为p被释放时候就是野指针了。后续如果接着调用p,可能会导致程序崩溃//所以要在使用完内存之后立即将p置为NULLp = NULL;return 0;
}

calloc函数

        calloc函数也可以用来动态内存分配,语法结构如下:

void*calloc(size_t num,size_t size)

        功能是给num个size元素开辟一块空间,并将其初始化为零。

        看着与malloc的功能相似,区别是calloc在返回地址之前吧申请的空间每个字节全部初始化为0。

        malloc效率更高一点,calloc不需要初始化。

realloc函数

        realloc函数让动态内存更加灵活的调整。

        如果发现申请空间过小或者过大的时候,为了合理使用内存,灵活的调整内存的大小,而realloc函数就是为了这个而生的。

        它的语法结构如下:

void*realloc(void *ptr,size_t size)

        ptr是要调整的内存的起始位置,size是调整后新的内存大小(单位为字节)

        返回值为调整后内存起始位置。

        这个函数调整原有内存的大小基础上会将原数据迁移到新空间。

 使用realloc几种情况

1.后面有足够大的空间,直接扩容。

2.后面空间足够但是被占用了,所以在新空间找一块足够大满足条件的内存空间,将旧空间数据拷贝到新的空间,随后释放掉旧空间并返回新空间的地址

动态内存管理的几个常见错误

对空指针解引用

        

#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i < 10; i++){*(p + i) = i=1; //可能产生空指针解引用操作}return 0;
}

所以要判断malloc返回值

对动态开辟内存的越界访问

        之前我们知道数组是不能越界访问的。动态内存也是如此,申请的时候也是有大小的,必须要在自己的范围内使用,超出范围就是非法访问。
        错误写法

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i <= 10; i++){*(p + i) = i=1; //当i为10的时候形成越界访问了}return 0;
}

对非动态内存使用free函数

错误写法

#include<stdio.h>
#include<stdlib.h>
int main()
{int a = 10;int* p = &a;//使用*p = 100;free(p);p = NULL;return 0;
}

 使用free函数释放了一部分

错误写法:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int)*10);if (p == NULL){printf("内存分配失败!\n");return -1;}int i = 0;for (i = 0; i < 5; i++){*p = 5;p++;}free(p);//p指向的不再是动态开辟的空间的起始地址。p = NULL;return 0;
}

同一动态内存多次释放

               错误写法:

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("内存分配失败!\n");return 1;}free(p);free(p);//释放两次,第二次释放会导致程序崩溃。
}
int main()
{test();return 0;
}

      可以这样改正

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("内存分配失败!\n");return 1;}free(p);p = NULL;free(p);//释放两次,第二次释放会导致程序崩溃。
}
int main()
{test();return 0;
}

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

        错误写法:

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}
}
int main()
{test();while (1);//无法知道前面申请10个字节的地址return 0;
}

正确写法:要在函数之内释放内存

        或者也可以这样

#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}return p;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//释放pr = NULL;while (1);//无法知道前面申请10个字节的地址return 0;
}

        只要保证一个原则:malloc、calloc、realloc必须要和free函数成对出现。

        realloc函数也能实现malloc函数的效果

        但是即使你成对存在,也可能内存泄漏

        如下图所示,在test函数中,在释放内存之前就已经返回了,所以内存没有释放,因此内存泄漏。

#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}int n = 20;if (n > 10){//代码}return p;free(p);p = NULL;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//释放pr = NULL;while (1);//无法知道前面申请10个字节的地址return 0;
}

一些经典的内存方面的例题:

1.

void GetMemory(char *p)
{p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(str);strcpy(str, "Hello World!");printf("%s\n", str);
}

运行test()函数后的结果:

运行崩溃。

解析:这里面,test函数中GetMemory函数的调用是直接将指针变量str本身传递过去了,是传值调用,str的值没有变化,仍然是NULL,所以在下一步进入strcpy函数,在strcpy函数中会对NULL进行解引用,造成了非法访问,程序就会崩溃。

可以这么更改:(这种方法更好一点)

void GetMemory(char **p)
{*p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}

也可以这样改 :

char* GetMemory(char **p)
{*p = (char*)malloc(100);return p;
}
void test()
{char* str = NULL;str=GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}

2.

char *GetMemory()
{char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}

 运行test函数的后果:

运行结果错误。

解析:p的地址可以正常传递给str,但是p数组是函数的局部变量,出了函数就会被回收,p数组的内收可能就被改了。这个就是返回栈空间地址的问题。

栈区上空间要么free函数回收,要么程序结束回收。

可以这样改:

char *GetMemory()
{static char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}

3.

void GetMemory(char **p,int num)
{*p=(char*)malloc(num);
}
void test()
{char* str = NULL;GetMemory(&str,100);strcpy(str,"hello");printf(str);
}

求test函数的运行结果

 程序崩溃

解析:内存没有释放。

4.

void test()
{char *str=(char *)malloc(100);strcpy(str,"hello");free(str);if(str!=NULL){    strcpy(str,"world");printf(str);}
}

求运行test函数的结果:

运行错误。

解析:str在free函数之后没有置为NULL。

这些题目出自于《高质量C/C++编程》

柔性数组

        柔性数组在结构体中,且最后一个成员是未知大小的数组,这个数组就是柔性数组。

struct S
{int a;int S[];//未指明大小,就是柔性数组
};

        有些编译器可能不支持这种写法,可以改成

struct S
{int a;int S[0];//未指明大小,就是柔性数组
};

柔性数组的特点

        结构体中柔性数组前至少要有一个成员

        sizeof返回这种结构大小不包括柔性数组

        包含柔性数组的结构用malloc进行动态内存分配,并且分配的内存应该大于该结构的大小以适应柔性数组预期大小。

#include<stdio.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性数组
}st;
int main()
{printf("%zd\n", sizeof(st));return 0;
}

结果为5。

        柔性数组的使用

        

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性数组
}st;
int main()
{st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10个int的空间if (p == NULL){perror("malloc error");return -1;}//使用内存p->a = 10;p->c = 0;for (int i = 0; i < 5; i++){p->S[i] = i+1;}//空间不够// 扩容st*q=(st*)realloc(p, sizeof(st) + 40 * sizeof(int));if (q != NULL){p = q;q = NULL;}//释放内存free(q);q = NULL;return 0;
}

应用二:相当于获得了10个整型元素空间

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int S[];//未指明大小,就是柔性数组
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10个int的空间p->i = 100;for (i = 0; i < 5; i++){p->S[i] = i+1;}free(p);p = NULL;return 0;
}

        柔性数组的优势

        上图代码也可以这样写:

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int *p_a;
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10个int的空间p->i = 100;p->p_a = (int*)malloc(p->i*sizeof(int));for (i = 0; i < 5; i++){p->p_a[i] = i+1;}free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

        二者 均可,但是方法一有两大好处:

1.方便内存释放

2.有利于访问速度

C/C++中内存区域划分

C/C++中内存划分的几个区域

1.栈区(stack):在执行函数时,函数内部局部变量的储存单元都可以在栈上创建。函数执行结束时这些储存单元自动被释放。栈内存分配内置于处理器指令集中,效率很高,但是分配的内存容量有限。栈区主要是存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等(详细了解可以参考《函数栈帧的创建与销毁》)

2.堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束可能由操作系统释放。分配方式类似于链表

3.数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放

4.代码段:存放函数体(类成员函数和全局函数)的二进制代码段

具体可以参考如下:

        

感谢看到这里的读者大大们,求一个赞,谢谢

http://www.xdnf.cn/news/3428.html

相关文章:

  • VSCode插件Python Image Preview使用笔记
  • 【FreeRTOS-列表和列表项】
  • PyTorch中“原地”赋值的思考
  • QT —— 信号和槽(带参数的信号和槽函数)
  • Qwen3 正式发布
  • Ethan独立开发产品日报 | 2025-04-30
  • Java中修饰类的关键字
  • [蓝桥杯 2021 省 AB] 砝码称重 Java
  • 【论文速递】2025年08周 (Robotics/Embodied AI/LLM)
  • Y1代码AC集
  • 坚鹏:平安保险集团《保险行业发展趋势与AI应用方法及案例》培训
  • 【Redis】Another Redis Desktop Manager 安装指南
  • 深入理解虚拟机与容器:原理、对比与应用场景分析
  • 动态规划简单题2
  • 算法-堆、排序算法、矩阵乘法
  • 面试手撕——迭代法中序遍历二叉树
  • 负载均衡深度实践:基于Nginx+Keepalived的高可用方案与Zabbix监控设计
  • Cesium Entity动态更新
  • 嵌入式AI还是一片蓝海
  • Day107 | 147.对链表进行插入排序 | 简单选择、冒泡、直接插入
  • 【专题五】位运算(2)
  • AXI中的out of order和interleaving的定义和两者的差别?
  • OSPF的路由
  • Go-web开发之社区功能
  • Java 中那些奇怪的空指针报错场景及解决方案NullPointerException
  • 【计算机视觉】语义分割:MMSegmentation:OpenMMLab开源语义分割框架实战指南
  • MySQL数据同步之Canal讲解
  • 2025年- H16-Lc124-169.多数元素(技巧)---java版
  • 7.0/Q1,GBD数据库最新文章解读
  • ClackyAI:下一代智能云开发环境的技术革新与实践价值