【C语言】内存你知多少?详解C语言动态内存管理

news/2024/11/24 2:23:15/

 

目录

一, 计算机中的内存

二,动态内存申请函数

2.1 头文件

2.2  malloc函数

2.3 free函数

2.3 calloc函数

2.4 realloc函数——调整空间函数

情况1:原有空间之后有足够大的空间

情况2:原有空间之后没有足够大的空间  

2.5 经典笔试题

 1.

2. 

三,柔性数组

结语


一, 计算机中的内存

    我们知道目前内存有,栈区,堆区,静态区。

C/C++程序内存分配的几个区域:

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

 目前我们有这些开辟内存空间的方法:

int  h  = 100;             //  在静态区创建全局变量int main(){static  z  = 10;         // 变量储存在静态区int val = 20;               //在栈空间上开辟四个字节char arr[10] = {0};     //在栈空间上开辟10个字节的连续空间return 0;}

但是上述的开辟空间的方式有两个特点

1. 空间开辟大小是固定的。

2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编 译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。 

二,动态内存申请函数

2.1 头文件

#include<stdlib.h>

2.2  malloc函数

void* malloc (size_t size);  // szie 字节数的意思,可以直接填数字

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

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

  • 如果开辟成功,则返回一个指向开辟好空间的指针
  • malloc可以申请0字节的空间,会返回一个没有空间的指针,不能访问。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定

 运用:

int * a = (int *)malloc(4); // 4可以改成 sizeof(int)
if	(a == NULL)
{perror("malloc"); // 开辟失败打印原因
}

2.3 free函数

void free (void* ptr);

free函数用来释放动态开辟的内存。

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。
  • 当我们不释放动态申请的内存时,如果持续申请内存,那么电脑内存不断变少,没有内存时就会死鸡,当程序结束时,操作系统自动回收内存。
  • 如果程序不结束,那么不回收的内存,会越来越多,这就是出现内存泄露问题。

2.3 calloc函数

void* calloc (size_t num, size_t size);

参数解析:

  • num:  创建数据类型的个数。
  • size:  每个数据类型所占的字节数

特点: 

  •  函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。 
#include <stdio.h>
#include <stdlib.h>
int main()
{int* a = (int*)malloc(8); // 2个整型int* b = (int*)calloc(2,sizeof(int)); // sizeof(int) 可以是 4,反正表示字节数return 0;
}

 查看内存验证:

2.4 realloc函数——调整空间函数

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

参数解析:

  • ptr :是要调整的内存地址。(如果为空指针,那么同malloc功能类似。)
  • size :调整之后新大小
  • 返回值 : 为调整之后的内存起始位置。

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

 realloc 函数就可以做到对动态开辟内存大小的调整。 函数原型

如下:

     这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

realloc在调整内存空间的是存在两种情况:

  • 情况1:原有空间之后有足够大的空间

  • 情况2:原有空间之后没有足够大的空间  

 

 看以下代码,思考代码那里不合理:

#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 10;int* p = &a;p = (int*)realloc(p, sizeof(int));printf("%d", *p);return 0;
}

 我们可以看出,这里没有判断realloc是否成功,如果申请失败,返回NULL,那么p就会丢掉原有的地址,这是不合理的,因此我们需要进行判断,所以正确的代码是:

#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 10;int* p = &a;int *tmp= (int*)realloc(p, sizeof(int));if (tmp == NULL){perror("realloc");return -1;// 或者exit(-1);}p = tmp;printf("%d", *p);return 0;
}

2.5 经典笔试题

 1.

   思考:程序结果

char* GetMemory(void)
{char p[] = "hello world"; // 栈区开辟,函数结束内存收回return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();  // 非法访问。访问被系统收回的内存printf(str);        // 无法打印,已被覆盖
}int main()
{Test();return 0;
}

2. 

  程序中存在的问题

void Test(void)
{char* str = (char*)malloc(100); // 缺少对malloc返回值检查/*加上:if(str == null){perror("malloc");exit(-1);}*/strcpy(str, "hello");free(str);      // 加上 str = null;if (str != NULL)         // str还存有原先内存的地址,出现野指针 {strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}

三,柔性数组

     也许你从来没有听说过柔性数组(flflexible array这个概念,但是它确实是存在的。 C99 中,结构中的最 后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如:

typedef struct st_type

{

  int i;

  int a[];  //柔性数组成员

}type_a;

 特点:

  • 结构中的柔性数组成员前面必须至少一个其他成员
  • sizeof 返回的这种结构大小不包括柔性数组的内存。

例如: 

typedef struct mystruct
{double a;int c[];
}MS;int main()
{printf("%d\n", sizeof(MS)); // 8return 0;
}
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

如下: 

typedef struct mystruct
{double a;int c[];
}MS;int main()
{MS * p1 = (MS* )malloc(sizeof(MS) + 40);  // 那就是48个字节,40就是对柔性数组申请的                                                                                                                                 //预期空间return 0;
}

也可以这样:

代码2
typedef struct mystruct
{double a;int *c;
}MS;
int main()
{MS * p1 = (MS* )malloc(sizeof(MS));  if  (p1 == NULL)
{perror ( "malloc");exit(-1);
}p1->c = (int *)realloc(c, 40);return 0;
}

 代码1相较于代码2的优势:

  • 方便释放内存。代码2需要2次释放内存。
  • 有利于提高数据命中率,提升运行效率,减少内存碎片。

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力


http://www.ppmy.cn/news/520810.html

相关文章

【工具】搜狗输入法常用配置(持续更新)

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ 按键相关通用快捷键系统快捷键辅助输入快捷键 2️⃣ 其它自定义语句关闭自动更新 &#x1f6ec; 结论 &#x1f6eb; 问题 描述 作为输入法的常青树&#xff0c;重装系统后经常第一步就是装输入法&#xff0c;由于以下原因&#…

PZT压电陶瓷产品碗形(弧形)、球形等陶瓷产品

PZT压电陶瓷产品 碗形&#xff08;弧形&#xff09;、球形等陶瓷产品&#xff0c;主要用于医学减肥和水声探测方面。 我们专业提供功能材料测试设备及测试服务&#xff1a; 一、压电材料测试装置&#xff1a;ZJ-3型精密D33测试仪&#xff0c;ZJ-4型宽量程精密D33测试仪&#…

全球与中国单晶炉市场现状及未来发展趋势

2020年&#xff0c;全球单晶炉市场规模达到了 亿元&#xff0c;预计2027年将达到 亿元&#xff0c;年复合增长率(CAGR)为 %。 本报告研究全球与中国市场单晶炉的产能、产量、销量、销售额、价格及未来趋势。重点分析全球与中国市场的主要厂商产品特点、产品规格、价格、销量、销…

环洋市场咨询:全球氧化铝陶瓷加热器收入预计2028年达到1.66亿美元

针对过去五年&#xff08;2017-2021&#xff09;年的历史情况&#xff0c;分析历史几年全球氧化铝陶瓷加热器总体规模&#xff0c;主要地区规模&#xff0c;主要企业规模和份额&#xff0c;主要产品分类规模&#xff0c;下游主要应用规模等。规模分析包括销量、价格、收入和市场…

MyBatis第七讲:MyBatis动态SQL

九、MyBatis动态SQL 9、1什么是动态SQL 动态 SQL 是 MyBatis 的强大特性之一。在 开发过程中&#xff0c;经常出现开发人员需要手动拼接 SQL 语句。根据不同的条件拼接 SQL 语句是一件极其痛苦的工作。例如&#xff0c;拼接时要确保添加了必要的空格&#xff0c;还要注意去掉…

压电陶瓷

作用&#xff1a; 1、压电陶瓷的原理是对这种陶瓷片施加压力还有存在一些拉力&#xff0c;导致它的两端会产生极性相反的一种电荷就是这样通过回路而变成了电流。 2、这种效应叫作压电效应&#xff0c;如果把这种压电陶瓷做成&#xff0c;在换能器放在水中&#xff0c;那么在…

python数字猜谜2.0

改进了一下数字猜谜&#xff1a; 开头&#xff0c;可选等级&#xff1a; import random guess -1 c 0 print("数字猜谜游戏&#xff01;") n input("选择等级 A B C&#xff1a;") if (n "A") or (n "a"):guess random.randint…

[进阶]junit单元测试框架详解

单元测试 就是针对最小的功能单元(方法&#xff09;&#xff0c;编写测试代码对其进行正确性测试。 以前是如何进行单元测试的&#xff1f;有什么问题&#xff1f; 只能在main方法编写测试代码&#xff0c;去调用其他方法进行测试。无法实现自动化测试&#xff0c;一个方法测…