内存函数和动态内存管理

目录

一、memcpy库函数介绍

1. memcpy的使用

2. memcpy的模拟

二、memmove库函数介绍

1. memmove的使用

2. memmove的模拟

三、memset库函数介绍

四、memcmp库函数介绍

五、动态内存中malloc和free

1. malloc

2. free

六、动态内存中calloc和realloc

1. calloc

2. realloc

七、柔性数组

1.1 柔性数组特点

1.2 柔性数组的使用

1.3 柔性数组与结构体内部成员指针区别

八、动态内存中使用常见错误

1. 对NULL指针的解引⽤操作

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

3. 对⾮动态开辟内存使⽤free释放

4. 使⽤free释放⼀块动态开辟内存的⼀部分

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

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

九、关于动态内存易错例题

1. 题目一

2. 题目二

3. 题目三

4. 题目四

十、总结c/c++中程序内存区域划分图


一、memcpy库函数介绍

1. memcpy的使用

        memcpy这个库函数用于是任何类型,将一个地址的内容复制到到另一个地址上,它是针对于内存的修改,使用使用过程中是memcpy(arr1(拷贝地址),arr2(从这里抄内容到arr1中),size_t(字节个数)),需要头文件string.h返回的是arr1这个已经拷贝的地址

它遇到\0不会停止,它只受字节个数的影响,它针对的是内存的修改,也就是一个一个字节的修改。注意这里是适用于不重叠的内存

2. memcpy的模拟

        根据以上的信息,可以知道memcpy的修改是通过一个一个字节的修改而以小改大。

#include<stdio.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{char* ret = dest;//记入起始地址//拷贝while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;//void*类型不能改变值,需要强制类型转换src = (char*)src + 1;}return ret;
}
int main()
{int arr1[20];int arr2[] = { 0,1,2,3,4,5,6,7 };void* ret = my_memcpy(arr1, arr2, sizeof(int) * 5);for (int i = 0; i <  5; i++){printf("%d ", ((int*)ret)[i]);}return 0;
}

 

二、memmove库函数介绍

1. memmove的使用

        memmove和memcpy的使用是一样的,不过它专门用于重叠内存的拷贝。

2. memmove的模拟

        menmove的情况就比较多了,因为对于重叠的内存,那就得考虑第一种是与需要拷贝字节内有重叠部分,这时候就是类型指针用于放入拷贝的内容地址比另一个指针类型用于输s出内容到拷贝指针小(也就是改变后,对后面拷贝没有影响第二种就是需要拷贝的字节内容有重叠部分,此时类型指针用于放入拷贝的内容地址比另一个指针类型用于输出内容到拷贝指针大(也就是改变后,对后面拷贝有影响)第三种就是拷贝字节不重叠(完全错开),这个是较为简单。直接上图来解释

#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, void* src, size_t num)
{assert(dest && src);void* ret = dest;//记入起始//判断重叠条件//第一种和第三种的一部分,从前往后直接替换即可if (dest < src){while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;//一个一个字节改变src = (char*)src + 1;}}//第二种和第三种的一部分,从后到前else{while (num--){*((char*)dest + num) = *((char*)src + num);//一步到位,用num来控制循环,还能使得从后到前改变每个字节//这里要注意的是一开始加的是19,而不是20,所以这里是num--}}return ret;
}
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,0 };void* ret = my_memmove(arr + 2, arr, sizeof(int) * 5);return 0;
}

 

对比memmove,模拟出来的恰好的正确的。这里需要注意的就是num--,为什么要这样设计。 

三、memset库函数介绍

        memset虽然是内存的修改,但是对于整型修改时,可能会不符合预期,memset的使用格式为(void*)memset(void*(地址),int(修改值字符或者数字),size_t(需要修改的字节个数))返回的是修改后的起始地址。上面也说了,memset是一个一个字节的修改对于要修改的值,所以对于数字整型4个字节的修改时,就不会符合预期。

每个字节都会修改成1,而读取整型时是通过4个字节4个字节的读取的,所以这里数字就会0x01010101 这16进制数字将会是很大的数,一般我们修改整型时是将其改为0。不过这里对于是一个字节类型,就没有影响,修改为什么值就是什么值。

四、memcmp库函数介绍

        memcmp是用来对比内存中对应的数字大小(这里字符也是数字,因为字符在内存中是以ascll值存储的),用法就是(int)memcmp(void* prt1,void* prt2,size_t(需要对比的字节个数))


#include<stdio.h>
#include<string.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 1,2,3,4,0x80000001 };//这里以小端存储,所以这里在内存存储是0x01 00 00 80//实际打印时还是打印出0x80 00 00 01int r = memcmp(arr1, arr2, 17);if (r > 0){printf("arr1 > arr2\n");}else if (r < 0){printf("arr1 < arr2\n");}else{printf("arr1 == arr2\n");}return 0;
}

这里是最好反映出memcmp的内部比较的形式,这里是以小端存储。对整型在内存的存储有兴趣的可以看这里 

☞ 整数和浮点数在内存中的存储-CSDN博客

五、动态内存中malloc和free

1. malloc

        malloc这个库函数是用与分配空间内存,需要头文件stdlib.h,用法为(void*)malloc (size_t(申请字节))这里申请成功返回该空间的起始地址,如果申请失败则返回空指针,这里分配的是连续可用的空间。

#include<stdio.h>
#include<stdlib.h>
int main()
{//开辟空间int* p = (int*)malloc(sizeof(int) * 10);if (p == NULL){perror("malloc");return 1;}//使用空间for (int i = 0; i < 10; i++){p[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", p[i]);}//释放空间free(p);p = NULL;return 0;
}

2. free

        这里free这个用法很简单,就是用来释放空间的函数,就好比图书馆借书,借了书肯定是要换的,这里开辟空间就类似借书这个过程,还书就是释放空间。(void) free(void* prt)内部是指向需要释放内存的这个空间,而prt指的是这个需要释放内存的这个空间的起始地址。这里必须是开辟的空间,否则会报错,如果释放的是空指针,那什么free什么都不做。

这里需要注意的是free释放完后,要养成一个好习惯,就是将这个指针变为空指针,因为后续如果要使用时,直接使用,那就是野指针的泛滥了。

六、动态内存中calloc和realloc

1. calloc

        calloc这个库函数和malloc这个类似,也是开辟空间的一个库函数,格式有所不同,就是(void*) calloc((元素个数),size_t(一个元素的字节)),都是开辟空间,只不过calloc开辟一个空间后是有自己的初始化内容的,这个初始化内容全为0;要说还有一个不一样的就是语法格式吧,malloc直接就是申请字节个数,而calloc是需要知道什么类型,该类型需要多少个元素。

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p1 = (int*)calloc(10, sizeof(int));if (p1 == NULL){perror("calloc");return 1;}//使用空间//释放空间free(p1);p1 = NULL;return 0;
}

左图为calloc这个库函数开辟的空间,右边为malloc这个库函数开辟的空间。 

2. realloc

        realloc这个用于扩容或缩减内存的大小,可以看需求给他增减空间大小,用法(void*)realloc((void*prt)表示需要扩容或缩减的地址,(size_t)表示扩容或者缩减后的大小)。如果扩容成功则会返回扩容成功后的起始地址并拷贝了扩容之前地址的内容,如果扩容缩减失败则会返回NULL空指针。这里扩容有2种情况

#include<stdio.h>
#include<stdlib.h>
int main()
{//开辟空间int* p = malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");return 1;}//使用空间for (int i = 0; i < 5; i++){p[i] = i + 1;}//不够扩容int* prt = (int*)realloc(p, sizeof(int) * 10);if (prt != NULL){p = prt;//将扩容的地址赋给p(这里主要是怕第二种情况)prt = NULL;}else{perror("realloc");return 1;}//使用//释放空间free(p);p = NULL;return 0;
}

realloc也可以实现malloc功能,realloc(NULL,40)等价于开辟40个字节空间,返回开辟后的空间。 这里如果是缩减空间,缩减过多可能会导致某个溢出字节数据丢失。

七、柔性数组

1.1 柔性数组特点

        柔性数组是指结构体中成员最后一项为一个数组并且还要是未知数组(没有[]大小),特点为:不参与整个结构体的大小前提是还没给它开辟空间。就是整个结构体大小不算这个数组大小)。

#include<stdio.h>
struct Stu1
{int age;char name;
};
struct Stu2
{int age;char name;int arr[];
};
int main()
{printf("%zd\n", sizeof(struct Stu1));printf("%zd\n", sizeof(struct Stu2));return 0;
}

 

1.2 柔性数组的使用

        柔性数组的使用一般配合动态内存函数一起使用,可以随时动态改变所需的大小,一般开辟空间大于结构体总大小,

#include<stdio.h>
#include<stdlib.h>
struct Stu
{int age;char name;int num[];
};
int main()
{//开辟空间struct Stu* s1 = (struct Stu*)malloc(sizeof(struct Stu) + sizeof(int) * 5);if (s1 == NULL){perror("malloc");return 1;}//使用s1->age = 18;s1->name = 'A';for (int i = 0; i < 5; i++){s1->num[i] = i + 1;}//空间不够struct Stu* ps = (struct Stu*)realloc(s1, sizeof(struct Stu) + sizeof(int) * 10);if (ps == NULL){perror("realloc");return 1;}//继续使用for (int i = 5; i < 10; i++){ps->num[i] = i + 1;}//释放free(ps);free(s1);s1 = NULL;ps = NULL;return 0;
}

1.3 柔性数组与结构体内部成员指针区别

        直接代码展示结构体内部成员为指针是如何和柔性数组一样操作的。

#include<stdio.h>
#include<stdlib.h>
struct Stu
{int age;char name;int* arr;
}*s1;
int main()
{//开辟s1 = (struct Stu*)malloc(sizeof(struct Stu));if (s1 == NULL){perror("malloc1");return 1;}//使用s1->age = 18;s1->name = 'A';s1->arr = (int*)malloc(sizeof(int) * 5);//申请if (s1->arr == NULL){perror("malloc2");return 1;}//使用for (int i = 0; i < 5; i++){s1->arr[i] = i + 1;}//扩容int* prt = (int*)realloc(s1->arr, sizeof(int) * 10);if (prt == NULL){perror("realloc");return 1;}else{s1->arr = prt;prt = NULL;}//使用//释放free(s1->arr);//先释放内部再释放外部s1->arr = NULL;free(s1);s1 = NULL;return 0;
}

一般我们都是使用柔性数组,这种用指针的代码量太大了,而且需要开辟两个连续的空间,合起来就不连续,会造成很多内存碎片(表示开辟空间过多,介于两个连续空间内部剩余的空间为内存碎片)。柔性数组的优点就是1.内存释放过程中方便,不像指针需要释放两次,而且还有顺序可言。2.有利于内存访问(连续内存便于访问)。 

八、动态内存中使用常见错误

1. 对NULL指针的解引操作

        开辟空间时,如果不判断是否为空指针,并且阻断后面进程,那就会报警告。

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){return 1;//必要性,否则会报错}//使用//释放free(p);p = NULL;return 0;
}

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

        对于已经开辟的空间,使用了未开辟的空间,会导致越界访问。

#include<stdio.h>
#include<stdlib.h>
int main()
{//开辟int* p = (int*)malloc(sizeof(int) * 10);if (p == NULL){perror("malloc");return 1;}//使用for (int i = 0; i <= 10; i++){*(p + i) = i + 1;//i=10时越界了}for (int i = 0; i <= 10; i++){printf("%d ", p[i]);}//释放free(p);p = NULL;return 0;
}

3. 对⾮动态开辟内存使⽤free释放

        对于不是动态函数开辟的内存,使用free释放,会导致程序错误。

#include<stdio.h>
#include<stdlib.h>
int main()
{int arr[10] = { 0 };int* p = (int*) & arr;free(p);p = NULL;return 0;
}

 

4. 使⽤free释放⼀块动态开辟内存的⼀部分

        我们知道free(*p)括号里面指向的是开辟空间,而p是开辟空间的起始地址,而释放一部分地址,也会导致程序错误,不能正常工作。

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");return 1;}p++;free(p);p = NULL;return 0;
}

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

        对一个已经开辟的动态内存多次释放。

#include<stdio.h>
#include<stdlib.h>
int main()
{//开辟int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");return 1;}//使用//释放free(p);free(p);p = NULL;return 0;
}

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

        在函数中开辟空间,如果不释放空间并且不返回该空间起始地址,会导致这一块空间浪费,既不能使用,也不能再次开辟这一块空间。

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p = (int*)malloc(sizeof(int) * 5);if (p == NULL){perror("malloc");p = NULL;}//忘记释放
}
int main()
{test();return 0;
}

九、关于动态内存易错例题

1. 题目一

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void GetMemory(char* p)
{p = (char*)malloc(100);//1.没有对NULL解引判断用//2.内存泄漏(没有释放空间)
}
void Test(void)
{char* str = NULL;GetMemory(str);//这里是传值调用strcpy(str, "hello world");//str为空指针printf(str);
}
int main()
{Test();return 0;
}

根据错误信息修改:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void GetMemory(char** p)
{*p = (char*)malloc(100);if (*p == NULL)//判断{perror("malloc");return;}
}
void Test(void)
{char* str = NULL;GetMemory(&str);//传址strcpy(str, "hello world");printf(str);free(str);//释放str = NULL;
}
int main()
{Test();return 0;
}

2. 题目二

#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{char p[] = "hello world";//函数中的局部变量在栈区离开后,会释放内存return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();//str已经改变//但是使用过程中会变为野指针printf(str);
}
int main()
{Test();return 0;
}

3. 题目三

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);//未释放
}
int main()
{Test();return 0;
}

4. 题目四

#include<stdio.h>
#include<stdlib.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;
}

十、总结c/c++中程序内存区域划分图

C/C++程序内存分配的⼏个区域:
1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建, 函数执⾏结束时 ,这些存储单元⾃动被释放 。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等
2. 堆区(heap): ⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)
回收 。分配⽅式类似于链表。
3. 数据段(静态区): (static)存放全局变量、静态数据。程序结束后由 系统释放
4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。

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

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

相关文章

yarn的基本介绍

1.Hadoop的三大结构及各自的作用&#xff1a; Hadoop是一个开源的分布式计算框架&#xff0c;它主要包括三大核心组件&#xff1a;HDFS&#xff08;Hadoop Distributed File System&#xff09;、YARN&#xff08;Yet Another Resource Negotiator&#xff09;和MapReduce。以…

STM32的启动方式

目录 一、从主闪存存储器启动&#xff08;Main Flash Memory&#xff09; 二、从系统存储器启动&#xff08;System Memory&#xff09; 三、从内置SRAM启动&#xff08;Embedded SRAM&#xff09; 四、从外挂存储介质启动的实现方式 1. 存储介质选型 2. 硬件连接 3. 引…

STC定时器频率占空比程序

// // 一、宏定义区 // #include <STC15.H> //头文件 #include <intrins.h> //库函数文件 #define FOSC 12000000L //IRC频率 typedef …

数据库服务器架构

ORM ORM&#xff08;Object Relational Mapping&#xff09;&#xff1a;对象与关系数据之间的映射 映射关系表&#xff1a; 类&#xff08;class&#xff09;—— 数据库的表&#xff08;table&#xff09; 对象&#xff08;object&#xff09;——记录&#xff08;record…

【论文速递】2025年04周 (Robotics/Embodied AI/LLM)

目录 DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning摘要 Evolving Deeper LLM Thinking摘要 Kimi k1.5: Scaling Reinforcement Learning with LLMs摘要 Agent-R: Training Language Model Agents to Reflect via Iterative Self-Train…

FortiAI 重塑Fortinet Security Fabric全面智能化进阶

专注推动网络与安全融合的全球性综合网络安全解决方案供应商 Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;&#xff0c;近日宣布&#xff0c;旗下 Fortinet Security Fabric 安全平台成功嵌入了 FortiAI 关键创新功能。这一举措将有效增强用户对各类新兴威胁的防护…

汽车免拆诊断案例 | 2019款大众途观L车鼓风机偶尔不工作

故障现象 一辆2019款大众途观L车&#xff0c;搭载DKV发动机和0DE双离合变速器&#xff0c;累计行驶里程约为8万km。车主进厂反映&#xff0c;鼓风机偶尔不工作。 故障诊断  接车后试车&#xff0c;鼓风机各挡位均工作正常。用故障检测仪检测&#xff0c;空调控制单元&#x…

MySQL为什么默认使用RR隔离级别?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL为什么默认使用RR隔离级别?】面试题。希望对大家有帮助&#xff1b; MySQL为什么默认使用RR隔离级别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL 默认使用 RR&#xff08;Repeatable Read&#xff09;…

目标检测篇---R-CNN梳理

目标检测系列文章 第一章 R-CNN 目录 目标检测系列文章&#x1f4c4; 论文标题&#x1f9e0; 论文逻辑梳理1. 引言部分梳理 (动机与思想) &#x1f4dd; 三句话总结&#x1f50d; 方法逻辑梳理&#x1f680; 关键创新点&#x1f517; 方法流程图补充边界框回归 (BBR)1. BBR 的…

Java技术栈 —— 基本规范

Java技术栈 —— 基本规范 一、接口文档生成工具二、接口设计2.1 开发顺序2.2 接口规范 三、数据类封装 一、接口文档生成工具 有很多jar包都支持swagger的接口文档&#xff0c;这样方便了接口测试&#xff0c;不需要用apifox自己写接口&#xff0c;直接调用文档里的swagger接…

Django ORM 定义模型

提示&#xff1a;定义模型字段的类型 文章目录 一、字段类型二、字段属性三、元信息 一、字段类型 常用字段 字段名描述备注AutoFieldint 自增必填参数 primary_keyTrue&#xff0c;无该字段时&#xff0c;django自动创建一个 BigAutoField&#xff0c;一个model不能有两个Au…

[密码学基础]GB与GM国密标准深度解析:定位、差异与协同发展

[密码学基础]GB与GM国密标准深度解析&#xff1a;定位、差异与协同发展 导语 在国产密码技术自主可控的浪潮下&#xff0c;GB&#xff08;国家标准&#xff09;与GM&#xff08;密码行业标准&#xff09;共同构建了我国商用密码的技术规范体系。二者在制定主体、法律效力、技术…

Day-1 漏洞攻击实战

实训任务1 漏洞攻击实战一 使用 御剑 得到网站后台地址 数据库登录与日志配置​​ 使用默认密码 root:root 登录phpMyAdmin&#xff0c;执行 SHOW VARIABLES LIKE general% 查看日志状态。 开启日志功能&#xff1a;set global general_log "ON";&#xff08;配图&…

leetcode 2563. 统计公平数对的数目 中等

给你一个下标从 0 开始、长度为 n 的整数数组 nums &#xff0c;和两个整数 lower 和 upper &#xff0c;返回 公平数对的数目 。 如果 (i, j) 数对满足以下情况&#xff0c;则认为它是一个 公平数对 &#xff1a; 0 < i < j < n&#xff0c;且lower < nums[i] …

011数论——算法备赛

素数筛 给定n, 求2~n内的所有素数 埃氏筛 利用素数的定义&#xff0c; 输出素数2&#xff0c;然后筛掉2的倍数&#xff0c;得 {2,3,5,7,9,11,13&#xff0c;…}输出素数3&#xff0c;然后筛掉3的倍数&#xff0c;得 {2,3,5,7,11,13&#xff0c;…} 继续上述步骤&#xff0…

算法之贪心算法

贪心算法 贪心算法核心思想常见应用场景典型案例案例一&#xff1a;找零问题案例二&#xff1a;活动选择问题案例三&#xff1a;货仓选址问题 贪心算法的应用详解霍夫曼编码最小生成树Dijkstra最短路径算法 总结 贪心算法 核心思想 贪心算法&#xff08;Greedy Algorithm&…

英码科技与泊川软件,携手加速AI与嵌入式系统融合创新

2025年4月15日&#xff0c;广州英码信息科技有限公司&#xff08;以下简称“英码科技”&#xff09;与广州泊川软件技术有限公司&#xff08;以下简称“泊川软件”&#xff09; 正式签署战略合作框架协议。此次合作将充分发挥双方在AI计算硬件与嵌入式操作系统领域的技术优势&a…

Flowable7.x学习笔记(九)部署 BPMN XML 流程

前言 到本篇为止&#xff0c;我们已经完成了流程定义以及其 BPMN XML 本身的查询和新增功能&#xff0c;那我们有有了XML之后就可以开始着手研究实现 Flowable7对流程的各种操作了&#xff0c;比如部署&#xff0c;挂起&#xff0c;发起等等。 首先第一步&#xff0c;我们本篇文…

electron 渲染进程按钮创建新window,报BrowserWindow is not a constructor错误;

在 Electron 中&#xff0c;有主进程和渲染进程 主进程&#xff1a;在Node.js环境中运行—意味着能够使用require模块并使用所有Node.js API 渲染进程&#xff1a;每个electron应用都会为每个打开的BrowserWindow&#xff08;与每个网页嵌入&#xff09;生成一个单独的渲染器进…

深入规划 Elasticsearch 索引:策略与实践

一、Elasticsearch 索引概述 &#xff08;一&#xff09;索引基本概念 Elasticsearch 是一个分布式、高性能的全文搜索引擎&#xff0c;其核心概念之一便是索引。索引本质上是一个存储文档的逻辑容器&#xff0c;它使得数据能够在高效的检索机制下被查询到。当我们对文档进行…