C语言----动态内存分配(malloc calloc relloc free)超全知识点

目录

一.动态内存函数 

1.malloc

2.free

3.calloc

4.malloc和calloc的区别

5.realloc

二.动态内存分配的常见错误

1.对null进行解引用操作

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

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

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

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

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

三.习题讲解

1.代码找错

(1)内存泄漏

(2)返回栈空间地址的问题

(3)非法访问内存

四.柔性数组

一.动态内存函数 

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

2.堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。分配方式类似于链表。
3.数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁

但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长

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

动态内存分配是在堆区进行的 

int val = 20;//在栈空间上开辟四个字节

char arr [ 10 ] = { 0 }; // 在栈空间上开辟 10 个字节的连续空间
开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,数组的编译时开辟空间的方式就不能满足了,这时候就只能试试动态内存开辟 

例如

虽然C语言是可以支持变长数组--c99中增加了,但是很多编译器是不支持c99的,所以变长数组没有办法使用,即arr[n],所以已有的分配内存空间的方式是局限的,所以要进行动态内存分配

struct S
{char name[20];int age;
}int main()
{int n=0;scanf("%d",&n);struct S arr[n];return 0;
}

1.malloc

void* malloc (size_t size)   动态内存开辟

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

malloc包含的几个要素 

//1.
#include<stdlib.h>
int main()
{
//2.int* p=(int*)malloc(10*sizeof(int));//malloc是void*型,所以要进行强制类型转换,但是在Gcc环境下或者说linux环境下是不需要进行转换的
}

代码如下(还没有回收释放空间)

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p=(int*)malloc(10*sizeof(int));if(p==NULL)printf("%s",strerror(errno));//开辟空间失败,可以用strerror显示错误结果else{for(int i=0;i<10;i++){//把每个元素打印出来 *(p+i)=i;}for(int i=0;i<10;i++){printf("%d",*(p+i));} }return 0;	
} 

 如果将其中的

    int* p=(int*)malloc(10*sizeof(int));

改为

     int* p=(int*)malloc(10*sizeof(INT_MAX));

系统会报错,错误信息为not enough space

(1)INT_MAX :INT_MAX 是 C++ 中 <climits> 头文件中定义的一个宏,用于表示 int 类型的最大值。该宏在 C 和 C++ 中都可以使用,他不是数据类型。

如果想正确使用INT_MAX开辟空间,代码如下

//在C++中使用new开辟一块新的空间
#include <iostream>
#include <climits> // 包含 INT_MAX 的头文件using namespace std; // 引入命名空间int main() {int* p = new int[10]; // 使用 new 关键字动态分配内存for (int i = 0; i < 10; ++i) {p[i] = INT_MAX; // 给每个元素赋值为 INT_MAX}for (int i = 0; i < 10; ++i) {cout << p[i] << " ";}cout << endl;delete[] p; // 释放动态分配的内存return 0;
}

2.free

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

void free ( void* ptr );
free 函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的。
如果参数 ptr 是 NULL 指针,则函数什么事都不做。

完整代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p=(int*)malloc(40);if(p==NULL)printf("%s",strerror(errno));//开辟空间失败,可以用strerror显示错误结果else{for(int i=0;i<10;i++){//把每个元素打印出来 *(p+i)=i;}for(int i=0;i<10;i++){printf("%d",*(p+i));}}//当动态申请的空间不再使用的时候//就应该还给操作系统free(p);//即使我们将p还给了操作系统,但是p依然指向这块空间,所以要进行p=NULLp=NULL;//使p不指向这块内存空间return 0;	
} 

 3.calloc

calloc 函数也用来动态内存分配。原型如下:
void* calloc ( size_t num , size_t size );
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为 0 。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0 

calloc的几个要素

//1.
#include<stdlib.h>
#include<malloc.h>
int main()
{
//2.
//malloc(10*sizeof(int))int *p=(int*)calloc(10,sizeof(int));return 0;}

代码如下

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<malloc.h>
int main()
{int* p=(int*)calloc(10,sizeof(int));if(p==NULL)printf("%s",strerror(errno));//开辟空间失败,可以用strerror显示错误结果else{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=NULL;return 0;	
} 

4.malloc和calloc的区别

1.malloc和calloc的开辟空间形式不同

2.calloc会初始化空间为0,而malloc不会初始化。所以malloc开辟空间效率更高,但不会将空间的每个字节初始化为0

5.realloc

realloc 函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void* realloc ( void* ptr , size_t size );
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
realloc 在调整内存空间的是存在两种情况:
情况 1 :原有空间之后有足够大的空间
对策:要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化
情况 2 :原有空间之后没有足够大的空间
对策:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

代码如下

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p=(int*)malloc(20);if(p==NULL)printf("%s",strerror(errno));//开辟空间失败,可以用strerror显示错误结果else{for(int i=0;i<10;i++){//把每个元素打印出来 *(p+i)=i;}}//假设这里20个字节的空间不够用了,我们希望使用40个字节的空间//这时候就可以使用realloc来调整动态开辟的空间int* p2=realloc(p,40);for(int i=5;i<10;i++){*(p2+i)=i;}for(int i=0;i<10;i++){printf("%d",*(p2+i));}return 0;	
} 

 在这里,用了p,p2两个指针指向不同的内存空间,这样p就不是统一管理所有的空间,所以这样做不对,追加空间有有以下两种情况:

1.开辟的空间后面正好有足够的空间能够追加内存,开辟内存空间后也是p指向内存首元素,返回的是p(旧的指针)

 2.如果开辟的空间后没有足够的空间,那么就重新开辟一块新的空间,把原来的地址里面的数据,拷贝到新开辟的更大的空间中

第一种方法返回的是旧的地址

第二种方法返回的是不同的新开辟的内存空间地址,旧的空间free()

注意:

int* p=realloc(p,40);

如果realloc内存开辟失败,返回NULL(空指针),那么p原来开辟的空间也找不到了,所以不能赋值到原来开辟的内存空间,应该这样写:

int* ptr=realloc(p,40);//用新变量接收realloc的返回值
if(ptr!=NULL)
{p=ptr;//仍然用p维护新的内存
} 
int i=0;

完整代码如下

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p=(int*)malloc(20);if(p==NULL)printf("%s",strerror(errno));//开辟空间失败,可以用strerror显示错误结果else{for(int i=0;i<5;i++){//把每个元素打印出来 *(p+i)=i;}}//假设这里20个字节的空间不够用了,我们希望使用40个字节的空间//这时候就可以使用realloc来调整动态开辟的空间int* p2=realloc(p,40);if(p2!=NULL){p=ptr;for(i=5;i<10;i++){*(p+i)=i;}for(i=0;i<10;i++){printf("%d",*(p+i));}}free(p);p=NULL;return 0;	
} 

二.动态内存分配的常见错误

1.对null进行解引用操作

int main()
{int *p=(int*)malloc(40);//万一解引用失败,p就被赋值为null*p=0//err;int i=0;for(int i=0;i<10;i++){*(p+i)=i;    }free(p);p=NULL;return 0;}

所以在解引用操作前要作出判断

int main()
{int *p=(int*)malloc(40);//万一解引用失败,p就被赋值为nullif(p!=NULL){*p=0//err;int i=0;for(int i=0;i<10;i++){*(p+i)=i;    }free(p);p=NULL;return 0;}
}

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

int main()
{int *p=(int*)malloc(5*sizeof(int));if(p==NULL)return 0;else {for(int i=0;i<10;i++)//本来只有5个整型元素,访问10个的话会越界{*(p+i)=i;}}//free(p);p=NULL;return 0;
}

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

int a=10;
int* p=&a;
*p=20;
free(p);
p=NULL;

a的空间是在栈区存放的,程序会出错

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

#include<stdio.h>
{
int *p=(int*)malloc(40);
if(p==NULL)
{return 0;
}
int i=0;
for(i=0;i<10;i++)
{*p++=i;
}
free(p);
p=NULL;//在这里p已经变化了,不是最初指向的空间了
return 0;
}

 代码应该改为

for(i=0;i<10;i++)
{*(p+i)=i;
}

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

int *p=(int*)malloc(40);
if(p==NULL)return 0;
else{....free(p);free(p);p=NULL;return 0;
}

或者这样,这样不会报错(每次释放完后,p所指向的地址置为空指针)

free(p);
p=NULL;
free(p);

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

while(1)
{malloc(1);sleep(1000);    
}
return 0;

三.习题讲解

1.代码找错

(1)内存泄漏

 这里有几个错误

(1)调用完GetMemory之后,p是GetMemory中的一个形参变量,p在这个函数内有效,出了这个函数之后就无效了,等GetMemory函数返回之后,动态开辟内存尚未释放并且无法找到,所以会造成内存泄漏,所以在这里str还是空指针,不是有效的地址,所以

strcpy(str,"hello world");//str并没有指向有效的地址,而是一块空指针

(2)没有free(),会出现内存泄露问题

void GetMemory(char **p)//对char *的地址解引用就是**p,p中存放的是str的地址,那么*p就是str
{*p=(char *)malloc(100);
}void Test(void)
{char *str=NULL;GetMemory(&str);strcpy(str,"hello world");printf(str);free(str);str=NULL;
}int main()
{Test();return 0;
}

或者

char* GetMemory(char *p)//对char *的地址解引用就是**p,p中存放的是str的地址,那么*p就是str
{p=(char *)malloc(100);return p;
}void Test(void)
{char *str=NULL;str=GetMemory(str);strcpy(str,"hello world");printf(str);free(str);str=NULL;
}int main()
{Test();return 0;
}

(2)返回栈空间地址的问题

 (1)str=GerMemory();//确实将返回值放到了str中,但是执行完该代码后,p的空间就还给操作系统了,所以当printf(str)打印时,str指向哪块空间就不清楚了,同理

int *test()
{int a=10;return &a;
}int main()
{int *p=test();//非法访问内存空间*p=20;return 0;}

可以改为

int *test()
{//int a=10;//栈区static int a=10;//将a放在静态区中,栈空间的地址返回存在风险,但是静态区不会return &a;
}int main()
{int *p=test();*p=20;return 0;}

也可以写为

int* test()
{int *ptr=malloc(100);//在堆区,如果不free依然存在return ptr;}int main()
{int *p=test();return 0;}

(3)非法访问内存

篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str成为野指针if(str!=NULL)语句不起作用

void test(void)
{char *str=(char*)malloc(100);strcpy(str,"hello");free(str);//虽然str开辟的区域已经还给操作系统了,但是str还是指向这块区域if(str!=NULL)//这里判断为真,world覆盖hello,打印了world,但是这块空间已经被释放了,不能使用了,但是还是打印了world,非法访问{strcpy(str,"world");printf(str);}
}int main()
{test();return 0;
}

代码修改为

void test(void)
{char *str=(char*)malloc(100);strcpy(str,"hello");free(str);//虽然str开辟的区域已经还给操作系统了,但是str还是指向这块区域str=NULL;//这样下面的判断(str!=NULL)才有意义if(str!=NULL){strcpy(str,"world");printf(str);}
}int main()
{test();return 0;
}

四.柔性数组

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

运用柔性数组 

struct S
{int n;int arr[];//未知大小的,柔性数组成员,数组大小是可以改变的
}int main()
{struct S s;printf("%d\n",sizeof(s));//结果为4struct S* ps=(struct S*)malloc(sizeof(struct S)+5*sizeof(int));
//为arr开辟了5个int型的地址空间ps->n=100;for(int i=0;i<5;i++){ps->arr[i]=i//0 1 2 3 4 5 }Struct *ptr=realloc(ps,44);//原来是24个字节,现在是44个字节,多个5个整型变量if(ptr!=NULL)ps=ptr;for(int i=5;i<10;i++){ps->arr[i]=i;}for(int i=0;i<10;i++){printf("%d",ps->arr[i]);  }free(ps);ps=NULL;return 0;
}

 另一种写法,不适用柔性数组,开辟空间的方式有一些区别,但是总体得到的结果相同

struct S
{int n;int* arr;}int main()
{struct S*ps = (struct S*)malloc(sizeof(struct S));ps->arr=malloc(5*sizeof(int));int i=0;for(i=0;i<5;i++){ps->arr[i]=i;}for(i=0;i<5;i++){printf("%d",ps->arr[i]);      }int *ptr=realloc(ps->arr,10*sizeof(int));if(ptr!=NULL)ps->arr=ptr;for(int i=5;i<10;i++)ps->arr[i]=i;for(int i=0;i<10;i++)printf("%d",ps->arr[i]);//释放内存,注意先后顺序不能改变free(ps->arr);ps->arr=NULL;free(ps);ps=NULL;return 0;
}

柔性数组的优点(第一种对于第二种而言的优点)

1.第二种运用了两次malloc,就要使用两次free(),出错概率更高

2.柔性数组相当于第二种方法,内存碎片更少了,内存利用率更高

3.柔性数组开辟的空间内存是连续的,访问效率更高,而第二种方法不是连续的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/24487.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

物联网|按键实验---学习I/O的输入及中断的编程|读取I/O的输入信号|中断的编程方法|轮询实现按键捕获实验-学习笔记(13)

文章目录 实验目的了解擒键的工作原理及电原理图 STM32F407中如何读取I/O的输入信号STM32F407对中断的编程方法通过轮询实现按键捕获实验如何利用已有内工程创建新工程通过轮询实现按键捕获代码实现及分析1 代码的流程分析2 代码的实现 Tips:下载错误的解决 实验目的 了解擒键…

Leetcode-每日一题【剑指 Offer 09. 用两个栈实现队列】

题目 用两个栈实现一个队列。队列的声明如下&#xff0c;请实现它的两个函数 appendTail 和 deleteHead &#xff0c;分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素&#xff0c;deleteHead 操作返回 -1 ) 示例 1&#xff1a; 输入&#xff1a; [&…

springboot第34集:ES 搜索,nginx

#用search after解决深分页性能问题 #第一页 GET /bank/_search {"size": 10,"sort": [{"account_number": {"order": "asc"}}] }#第二页 GET /bank/_search {"size": 10,"sort": [{"account_numb…

【WEB逆向】前端全报文加密的分析技巧

由于前端全报文加密&#xff0c;无法从变量的全文搜索来快速定位加密函数对加密参数的定位&#xff08;全局搜索还有个弊病是编码混淆的js也不能全局搜到&#xff0c;需要进一步分析判定混淆的编码形式后再全局搜编码后的变量名&#xff09;&#xff0c;因此可利用xhr断点全局拦…

LLM reasoners 入门实验 24点游戏

LLM reasoners Ber666/llm-reasoners 实验过程 实验样例24games&#xff0c;examples/tot_game24&#xff0c;在inference.py中配置使用代理和open ai的api key。 首先安装依赖 git clone https://github.com/Ber666/llm-reasoners cd llm-reasoners pip install -e .然后…

【雕爷学编程】Arduino动手做(187)---1.3寸OLED液晶屏模块2

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

Spring Security OAuth2.0(7):自定义认证连接数据库

自定义认证连接数据库 首先创建数据库和用户表 CREATE TABLE t_user (id bigint(20) NOT NULL AUTO_INCREMENT,username varchar(64) DEFAULT NULL,password varchar(64) DEFAULT NULL,fullname varchar(255) DEFAULT NULL,mobile varchar(20) DEFAULT NULL,PRIMARY KEY (id)…

MacOS使用brew如何下载Nginx

首先&#xff0c;第一步切换源&#xff1a; 切换 brew.git 仓库地址&#xff1a; cd "$(brew --repo)" git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git 替换 homebrew-core.git 仓库地址: cd "$(brew --repo)/Library/Taps/home…

LabVIEW 开发在不确定路况下自动速度辅助系统

LabVIEW 开发在不确定路况下自动速度辅助系统 智能驾驶辅助系统是汽车行业最先进的升级和尖端技术&#xff0c;智能交通系统依靠智能驾驶辅助系统在公共交通部门工作。该智能驾驶辅助系统技术包括自适应巡航控制&#xff0c;防抱死制动系统&#xff0c;安全气囊展开&#xff0…

【机器学习】编码、创造和筛选特征

在机器学习和数据科学领域中&#xff0c;特征工程是提取、转换和选择原始数据以创建更具信息价值的特征的过程。假设拿到一份数据集之后&#xff0c;如何逐步完成特征工程呢&#xff1f; 文章目录 一、特性类型分析1.1 数值型特征1.2 类别型特征1.3 时间型特征1.4 文本型特征1.…

图像 检测 - RetinaNet: Focal Loss for Dense Object Detection (arXiv 2018)

图像 检测 - RetinaNet: Focal Loss for Dense Object Detection - 密集目标检测中的焦点损失&#xff08;arXiv 2018&#xff09; 摘要1. 引言2. 相关工作References 声明&#xff1a;此翻译仅为个人学习记录 文章信息 标题&#xff1a;RetinaNet: Focal Loss for Dense Obje…

CentOS 搭建 Harbor 镜像仓库(图文详解)

本文目录 1. 下载 Harbor 安装包2. 解压3. 修改配置文件4. 安装 Harbor5. 修改 docker 配置6. docker 登录方式7. 访问 Harbor Web 界面8. 创建证书9. 生成证书10. 更新配置11. 网页登录 说明&#xff1a;在搭建 Harbor 镜像仓库之前&#xff0c;虚拟机要先安装 docker 和 dock…

数据安全治理5大关键技术实践分享

近年来&#xff0c;国内外对数据安全的重视程度持续提升&#xff0c;数据安全技术领域发展备受关注。从2017-2021年Gartner发布的“数据安全技术成熟度曲线”研究报告来看&#xff0c; 新兴数据安全技术呈逐年递增趋势&#xff0c;其中安全多方计算、同态加密、差分隐私等隐私增…

增强型Web安全网关在银行的应用

销售&#xff0c;绝不是降低身份去取悦客户&#xff0c;而是像朋友一样给予合理的建议。你刚好需要&#xff0c;我刚好专业&#xff01;仅此而已&#xff01; 乔.吉拉德 健康的安全体系&#xff0c;还可以更完善 浙江某商业银行股份有限公司是一家成立多年的商业银行&#xf…

linux 系统初始化基本yum命令

安装可能用到的系统工具 yum -y install vim telnet wget net-tools lrzsz unzip zip 安装常用工具和开发包 yum install -y which openssh-clients openssh-server less iproute bzip2 cmake gcc gcc-c gdb git libtool make man net-tools sysstat sudo psmisc nc net-t…

vue中transition动画的使用

1.vue文件 说明&#xff1a;加name属性 <transition name"sort"><div class"sort" v-show"show"><div class"all-sort-list2" click"goSearch"><div class"item bo" v-for"(item1, in…

windows系统的IP、路由、网关、内外网同时访问路由以及修改系统文件hosts的配置

当我们刚刚入职一家公司的时候、一般公司会给我下发一个ip地址和mac地址、还有访问一些公司的平台需要修改hosts之后的路由配置、以及第一次配置内网、如何内外网同时上网。 目录 一、ip的配置 1.1、IP的配置 1.2、mac地址的配置 1.3、内外网路由的配置&#xff08;w11系统需…

小程序学习(五):WXSS模板语法

1.什么是WXSS WXSS是一套样式语言,用于美化WXML的组件样式,类似于网页开发中的CSS 2.WXSS和CSS的关系 WXSS模板样式-rpx 3.什么是rpx尺寸单位 4.rpx的实现原理 5.rpx与px之间的单位换算* WXSS模板样式-样式导入 6.什么是样式导入 使用WXSS提供的import语法,可以导入外联的样式…

前端js--剪刀石头布

效果图 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><linkrel"stylesheet"href"ht…

微服务——操作索引库+文档操作+RestClient操作索引库和文档(java程序)

索引库操作 mapping属性 mapping是对文档的约束&#xff0c;常见约束属性包括: 创建索引库 #创建索引库 PUT /heima {"mappings": {"properties": {"info":{"type": "text","analyzer": "ik_smart"},…