【进阶C语言】动态内存分配
本章大致内容介绍:
1.malloc函数和free函数
2.calloc函数
3.realloc函数
4.常见错误案例
5.笔试题详解
6.柔性数组
一、malloc和free
1.malloc函数
(1)函数原型
函数参数:根据用户的需求需要开辟多大的字节空间,为无符号的字节。
返回值:malloc函数成功开辟内存后,会返回该内存的起始地址,可以根据需要强制转换成任意的类型;若开辟空间失败,则会返回空指针(NULL)。
头文件:#include<stdlib.h>
(2)使用方法
1)申请空间:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(40);return 0;
}
目的:申请十个整形空间,所以参数传:4*10=40。
结果:用一个整形指针来接收其返回值
2)检查安全和使用
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(40);if (p == NULL)//必须对指针安全性检查{printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i=0;i<10;i++){*(p+i) = i;}i = 0;for (i=0;i<10;i++){printf("%d\n",*(p+i));}return 0;
}
当使用结束之后,我们需要删除该动态生成的空间,则需要对空间进行释放,这就是我们接下来讲的free。
2.free函数
(1)函数原型
1.参数为动态开辟内存的首地址
2.无参数返回
3.头文件#include<stdlib.h>
(2)配合动态内存开辟的函数使用
前面malloc函数开辟的内存还没释放,接下来它们配合使用。
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i=0;i<10;i++){*(p+i) = i;}i = 0;for (i=0;i<10;i++){printf("%d\n",*(p+i));}free(p);p = NULL;//及时将指针置空return 0;
}
注意事项:
1.free只能释放由动态内存开辟的空间
2.free释放的是指针所指向的那块空间,释放后指针仍在,但是指向的空间不咋了,就会变成野指针,所以我们需要及时置空。
二、calloc
1.函数定义
函数参数:第一个参数是需要开辟的数据个数,第二个是该数据类型的内存大小。
返回值:calloc函数成功开辟内存后,会返回该内存的起始地址,可以根据需要强制转换成任意的类型;若开辟空间失败,则会返回空指针(NULL)。
头文件:#include<stdlib.h>
2.calloc的使用
目的:需要开辟10个整形空间
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10,sizeof(int));if (p == NULL){printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}i = 0;for (i = 0; i < 10; i++){printf("%d\n", *(p + i));}free(p);p = NULL;//及时将指针置空return 0;
}
运行结果:
根据malloc和calloc函数使用的两段代码,好像除了名字和参数之外,其他没什么不同呀?其实他们还有一处区别。
3.calloc函数与malloc函数的区别
(1)区别
malloc函数开辟好空间之后,并不会对其初始化,但是calloc函数开辟好空间之后,会将数据的每一个字节都初始化成0。
(2)对照
1)malloc
2)calloc
除了以上三点不同之外,其他的都一样。所以我们需要根据内存需求,需不需要初始化内存而选择合适的开辟方式。
三、realloc
1.函数定义
realloc可以对已有的内存进行调整
函数参数:ptr是要调整的内存地址,size为内存调整之后的新大小,单位是内存总大小(字节)
返回值:内存调整后的起始地址,同样有申请内存成功和失败两种情况
头文件:#include<stdlib.h>
2.realloc申请空间成功的两种情况
(1)原空间后的空间足够大
开辟空间方式:直接原有内存之后直接追加空间,原来空间的数据不发生变化。
(2)原空间之后没有足够大的空间
原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。而原有数据也会被拷贝到新内存中。
3.realloc的使用
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10,sizeof(int));if (p == NULL){printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}i = 0;for (i = 0; i < 10; i++){printf("%d\n", *(p + i));}//要求加大空间内存int* ptr = realloc(p,40*sizeof(int));if (ptr == NULL){printf("内存开辟失败\n");return;}p = ptr;//将新开辟好的内存赋值原地址free(p);p = NULL;//及时将指针置空return 0;
}
realloc的使用一般在原有空间的情况下,同样也需要对指针进行判空操作和free。
我们也可以看到,free和这些函数是紧紧联系在一起的。
四、常见错误解析
这些错误都是动态内存开辟前后的问题,与指针也有很大的联系
1.对NULL指针的解引用操作
错误写法:
int main()
{int* p = (int*)malloc(4);*p = 20;printf("%d\n",*p);return 0;
}
malloc有可能开辟动态内存失败,则会返回NULL,这个时候对NULL指针解引用操作就是非法的。
正确写法:
int main()
{int* p = (int*)malloc(4);*p = 20;if (p == NULL)//对指针安全性限制{perror(malloc);return;}printf("%d\n",*p);
//后续需要对内存释放return 0;
}
知识点1:在每次动态内存开辟完成之后,都要先对其指针进行判空操作;若非空,才能对其进行后续的操作。
2.对动态开辟空间的越界访问
错误写法:
int main()
{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);
}
错误的后果提示:
知识点2:在对指针解引用操作时,要注意指针所指向的个数
3.对非动态开辟内存使用free释放
错误写法:
test()
{int a = 100;int* p = &a;free(p);//错误
}
知识点3:free函数只能释放动态开辟的内存,否则会非法。
4.使用free释放一块动态开辟内存的一部分
错误写法:
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
当p++之后,p指向的起始位置就变了,当free(p)之后,会释放不完整,也会造成内存泄漏。
知识点4:使用指针,尽量不要改变指针指向的起始地址。可以再重新使用新指针进行++或--操作;或者+1/-1操作。
5.对同一块动态内存多次释放
错误写法:
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
知识点5:切记要对内存释放,但是每一块内存有且只能释放一次。
6.动态开辟内存忘记释放(内存泄漏)
错误:
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
这是忘记对动态内存的释放的,也是不可取的。
五、关于动态内存开辟的笔试题
分析下面四道代码题存在什么问题
运行Test函数会有什么样的后果
1.对NULL解引用操作
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
1.str指针为空
2.malloc开辟的空间只是被p指向,没有被str指向(相当于形参的改变不影响实参)
3.所以strcpy函数就会对NULL指针进行解引用操作
4.没有free操作,还会操作内存泄漏
图解:
正确写法:
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);free(str);str=NULL;
}
2.
问题代码:
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
1.字符数组p为栈空间的局部变量,函数返回后会被销毁
2.数组被销毁,返回的p就是野指针(所指向的空间已不属于自己)
类型代码情况:
int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();printf("%d\n",*p);return 0;
}
这种运行的结果仍然可以得到10,虽然空间依然属于p,但是值仍在,没有被其他的数据覆盖。但是下面这种情况则不行。
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);
}
动态开辟的内存,最后没有被free释放
4.
问题代码:
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
1.str所指向的空间已被销毁
2.str变成野指针,对其解引用操作为非法
六、柔性数组
1.柔性数组的定义
标准定义:C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
也就是说,柔性数组不是指简单的数组,而是在结构体中的数组。
有两种写法:
第一种:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
a数组就称为柔性数组。但是这种定义方式容易报错,所以我们还有第二种。
第二种:
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
就是不需要指定数组的大小,数组的大小是未知的。
2.柔性数组的特点
(1)sizeof 返回的这种结构大小不包括柔性数组的内存。
(2)结构中的柔性数组成员前面必须至少一个其他成员。
因为柔性数组是不计入sizeof的计算的,只有柔性数组成员sizeof就会出错。
(3)包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
在计算包含柔性数组的结构体时,柔性数组是不计入内存的计算的。大于结构体内存大小的部分就会分配给柔性数组。
(4)代码验证
struct S
{int a;int arr[];
};
int main()
{struct S s;printf("%zd\n",sizeof(s));//计算该结构体的内存大小return 0;
}
运行的结果:
柔性数组确实是不会参加sizeof对结构体的计算
3.柔性数组的使用
(1)开辟空间
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct S
{int a;int arr[];
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S)+16);if (ps == NULL){perror(malloc);return;}return 0;
}
(2)增容(realloc函数)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct S
{int a;int arr[];
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S)+16);if (ps == NULL){perror(malloc);return;}struct S* str = (struct S*)realloc(ps,sizeof(struct S)+40);if (str != NULL){ps = str;}else{perror(realloc);return;}return 0;
}
用malloc开辟空间之后,再用reallo增容(减容)。增容之后的空间都会加在柔性数组上,这个时候数组的大小就可以根据realloc变化,因此称为柔性数组。
4.柔性数组的优势
(1)方便内存释放
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct S
{int a;int arr[];//定义一个柔性数组
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S)+16);if (ps == NULL){perror(malloc);return;}struct S* str = (struct S*)realloc(ps,sizeof(struct S)+40);if (str != NULL){ps = str;}else{perror(realloc);return;}free(ps);ps = NULL;return 0;
}
因为开辟的空间都是连续的,在一块内存中,所以只需要free一次即可。
我们再对比一下另一种写法就更加明显了。
结构体中有指针的写法:
struct S
{char c;int i;int* data;//定义一个指针
};
int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){perror("malloc1");return 1;}ps->c = 'w';ps->i = 100;ps->data = (int*)malloc(20);if (ps->data == NULL){perror("malloc2");return 1;}int i = 0;for (i = 0; i < 5; i++){ps->data[i] = i;}for (i = 0; i < 5; i++){printf("%d ", ps->data[i]);}//空间不够了,增容int* ptr = (int*)realloc(ps->data, 40);if (ptr == NULL){perror("realloc");return 1;}else{ps->data = ptr;}//增容成功就使用//...//释放free(ps->data);//第一次ps->data = NULL;free(ps);//第二次ps = NULL;return 0;
}
1.两次开辟空间的原因是使得他们的数据都开辟在堆区上
2.使得跟第一种一样的写法,突然第一种的优势
3.这种写法开辟的空间是不连续的,容易造成空间零碎空间,导致空间浪费。
(2)有利于访问速度和节约内存
连续的内存有益于提高访问速度,也有益于减少内存碎片,更大程度的利用内存空间。
相关文章:

【进阶C语言】动态内存分配
本章大致内容介绍: 1.malloc函数和free函数 2.calloc函数 3.realloc函数 4.常见错误案例 5.笔试题详解 6.柔性数组 一、malloc和free 1.malloc函数 (1)函数原型 函数参数:根据用户的需求需要开辟多大的字节空间ÿ…...

手机上记录的备忘录内容怎么分享到电脑上查看?
手机已经成为了我们生活中不可或缺的一部分,我们用它来处理琐碎事务,记录生活点滴,手机备忘录就是我们常用的工具之一。但随着工作的需要,我们往往会遇到一个问题:手机上记录的备忘录内容,如何方便地分享到…...

LeetCode 2251. 花期内花的数目:排序 + 二分
【LetMeFly】2251.花期内花的数目:排序 二分 力扣题目链接:https://leetcode.cn/problems/number-of-flowers-in-full-bloom/ 给你一个下标从 0 开始的二维整数数组 flowers ,其中 flowers[i] [starti, endi] 表示第 i 朵花的 花期 从 st…...

【3】贪心算法-最优装载问题-加勒比海盗
算法背景 在北美洲东南部,有一片神秘的海域,那里碧海蓝天、阳光 明媚,这正是传说中海盗最活跃的加勒比海(Caribbean Sea)。 有一天,海盗们截获了一艘装满各种各样古董的货船,每一 件古董都价值连…...
JavaScript 的 for 循环应该如何学习?
JS for 循环语法 JS for 循环适合在已知循环次数时使用,语法格式如下: for(initialization; condition; increment) {// 要执行的代码 }for 循环中包含三个可选的表达式 initialization、condition 和 increment,其中: initial…...

C++核心编程--对象篇
4.2、对象 4.2.1、对象的初始化和清理 用于对对象进行初始化设置,以及对象销毁前的清理数据的设置。 构造函数和析构函数 防止对象初始化和清理也是非常重要的安全问题 一个对象或变量没有初始化状态,对其使用后果是未知的同样使用完一个对象或变量&…...
安装php扩展XLSXWriter,解决php导入excel表格时获取日期变成浮点数的方法
安装php扩展XLSXWriter 1、下载安装包 PECL :: Package :: xlswriter #例如选择下载1.3.6版本 2、解压下载包 tar -zxvf xlswriter-1.3.6.tgz 3、进入文件夹,编译 cd xlswriter-1.3.6 phpize ./configure --with-php-config=/usr/local/php7.1/bin/php-config make&am…...

Vue+element开发Simple Admin后端管理系统页面
最近看到各种admin,头大,内容太多,根本不知道怎么改。所以制作了这个项目,只包含框架、和开发中最常用的表格和表单,不用自己从头搭建架构,同时也容易上手二次开发。可以轻松从其他开源项目整合到本项目。项…...

源码编译安装pkg-config
安装环境:银河麒麟 1 到这个网址下载pkg-config源码: Index of /releases (pkg-config.freedesktop.org) 2 解压 3 进入解压后的目录。输入 ./configure 但是报错。 4 根据报错信息,将configure改为: ./configure --with-i…...
游览器找不到服务器上PHP文件的一种原因
最近在练习搭建网站,遇到游览器找不到服务器上的php文件的问题。后来查找发现,apache文档根目录跟apache虚拟主机文档根目录不同,服务器开启了虚拟主机功能。这导致游览器找不到php文件。使用的环境LAMP 里操作系统和软件版本如下:…...
C++之std::function的介绍
C之std::function的介绍 std::function和函数指针的区别介绍std::function 的常见用法包括用法举例 std::function和函数指针的区别介绍 std::function 和函数指针在 C 中都可以用来存储和调用函数,但它们的使用方式和功能有所不同。 函数指针是一种指向函数的指针…...
卷积神经网络学习(一)
CNN应用对象是图像,CNN可被应用于的任务: 1、分类(classification):对图像按其中的物体进行分类,如图像中有人与猫,则图像可分为两类。 2、目标检测(object detection)&a…...

使用KEIL自带的仿真器仿真遇到问题解决
*** error 65: access violation at 0x40021000 : no read permission 修改debug选项设置为下方内容。...

4700 万美元损失,Xn00d 合约漏洞攻击事件分析
4700 万美元损失,Xn00d 合约漏洞攻击事件分析 基础知识 ERC777 ERC777 是 ERC20 标准的高级代币标准,要提供了一些新的功能:运营商及钩子。 运营商功能。通过此功能能够允许第三方账户代表某一合约或者地址 进行代币的发送交易钩子功能。…...

第5讲:v-if与v-show的使用方法及区别
v-if条件判断 v-if是条件渲染指令,它根据表达式的真假来删除和插入元素,它的基本语法如下: v-if “expression” expression是一个返回bool值的表达式,表达式可以是一个bool属性,也可以是一个返回bool的运算式 &#…...

C理解(一):内存与位操作
本文主要探讨C语言的内存和为操作操作相关知识。 冯诺依曼结构和哈佛结构 冯诺依曼结构:数据和代码放在一起,便于读取和修改,安全性低 哈佛结构是:数据和代码分开存放,安全性高,读取和修麻烦 内存 内存是用来存储全局变量、局…...

ESP8266使用记录(四)
放上最终效果 ESP8266&Unity游戏 整合放进了坏玩具车遥控器里 最终只使用了mpu6050的yaw数据,因为roll值漂移…… 使用了https://github.com/ElectronicCats/mpu6050 整个流程 ESP8266取MPU6050数据,处理后通过udp发送给Unity显示出来 MPU6050_Z…...

云原生Kubernetes:K8S安全机制
目录 一、理论 1.K8S安全机制 2.Authentication认证 3.Authorization授权 4.Admission Control准入控制 5.User访问案例 6.ServiceAccount访问案例 二、实验 1.Admission Control准入控制 2.User访问案例 3.ServiceAccount访问案例 三、问题 1.生成资源报错 2.镜…...

【数据结构】归并排序、基数排序算法的学习知识点总结
目录 1、归并排序 1.1 算法思想 1.2 代码实现 1.3 例题分析 2、基数排序 2.1 算法思想 2.2 代码实现 2.3 例题分析 1、归并排序 1.1 算法思想 归并排序是一种采用分治思想的经典排序算法,通过将待排序数组分成若干个子序列,将每个子序列排序ÿ…...

【C++】C++模板进阶 —— 非类型模板参数、模板的特化以及模板的分离编译
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C学习 🎯长路漫漫浩浩,万事皆有期待 上一篇博客:【C】C多…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...