C语言——内存函数

引言

在之前的两篇文章中,我们学习了字符函数和字符串函数,C语言中还有一类库函数叫做内存函数

我们接下来去学习一下这类函数

内存函数

内存函数的功能和某些字符函数的功能相似,它们是通过访问地址的方式操作对象,可应用于任何类型的对象。它们的使用需要包含头文件<string.h>

memcpy

1.memcpy的用法

memcpy用于内存拷贝。它的主要作用是将指定源地址处的内容复制到指定的目标地址处,即将一段内存中的数据拷贝到另一段内存中。

函数原型为:

void * memcpy ( void * destination, const void * source, size_t num );

其中,其参数的含义如下:

destination:指向目标内存区域的指针,即要接收源内存内容的位置

source:指向源内存区域的指针,即要复制的内容所在的起始位置

num:要拷贝的字节数

返回值:

返回一个指向目标内存区域destination的指针

功能:

函数memcpy从source的位置开始向后赋值num个字节的数据到destination指向的内存位置

注意:

memcpy不会对目标内存进行初始化或清除,只是简单地覆盖指定大小的字节

如果源地址和目标地址重叠,则memcpy的行为是未定义的。在这种情况下,应该使用memmove函数

使用memcpy时,必须确保目标内存区域destination有足够的空间来容纳要复制的n个字节,否则会导致缓冲区溢出,引发程序错误或安全漏洞

2.memcpy的使用

#include<stdio.h>  
#include<string.h>int main()
{int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };int arr2[10] = { 0 };memcpy(arr2, arr1, 20);for (int i = 0; i < 10; i++){printf("%d ", arr2[i]);}return 0;
}

输出结果为:

0 1 2 3 4 0 0 0 0 0

3.memcpy的模拟实现

思路:与strncpy类似,只不过memcpy是将指定长度的内存内容到目标地址,并返回目标地址的起始位置。

代码实现如下:

void* my_memcpy(void* dest,const void* src, int n)
{void* ret = dest;assert(dest && src);//循环复制内存内容,直到复制了 n 个字节while (n--){// 将源地址的当前字节复制到目标地址的当前位置 *(char*)dest = *(char*)src;		dest = (char*)dest + 1;src = (char*)src + 1;}return ret;
}int main()
{int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };int arr2[20] = { 0 };my_memcpy(arr2, arr1, 20);for (int i = 0; i < 10; i++){printf("%d ", arr2[i]);}return 0;
}

memmove

1.memmove的用法

memmove用于在内存中移动(复制)字节块。它的主要用途是处理源和目标内存区域重叠的情况,这是 memcpy 函数无法处理的

函数原型为:

void * memmove ( void * destination, const void * source, size_t num );

其中,其参数的含义如下:

destination:指向目标内存区域的指针,即要接收源内存内容的位置

source:指向源内存区域的指针,即要复制的内容所在的起始位置

num:要拷贝的字节数

返回值:

memmove 函数返回指向目标内存区域的指针,即 destination

注意:

使用 memmove 时,即使源和目标内存区域重叠,它也能正确复制数据。这是因为 memmove 可能会采用不同的策略来确保数据的一致性和正确性,比如先临时存储源数据,然后再复制到目标位置

2.memmove的使用

int main()
{int arr[] = { 0,1,2,3,4,5,6,7,8,9 };memmove(arr + 2, arr, 20);for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

输出结果为:

0 1 0 1 2 3 4 7 8 9

解释:

memmove 将数组的前5个整数(0, 1, 2, 3, 4)复制到了从第三个位置开始的内存

int main()
{int arr[] = { 0,1,2,3,4,5,6,7,8,9 };memmove(arr, arr + 2, 20);for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

输出结果为:

2 3 4 5 6 5 6 7 8 9

解释:

由于memmove将数组从第三个元素开始的五个整数(2, 3, 4, 5, 6)复制到了数组的开始位置

3.memmove的模拟实现

我们现在已经大概理解了memmove的用法,现在我们来试着模拟实现memmove

我们先来分析一下:

情况1:

    int arr[] = { 0,1,2,3,4,5,6,7,8,9 };memmove(arr + 2, arr, 20);

从2的位置开始拷贝:2->0  3->1  4->2  5->3  6->4 拷贝成功

从6的位置开始拷贝:6->4  5->3  4->2  3->1  2->0 拷贝失败

情况2:

	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };memmove(arr, arr + 2, 20);

从0的位置开始拷贝:0->2  1->3  2->4  3->5  4->6 拷贝失败

从4的位置开始拷贝:4->6  3->5  2->4  1->3  0->2 拷贝成功

情况3:

	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };memmove(arr, arr + 5, 20);

从5的位置开始拷贝:5->0  6->1  7->2  8->3  9->4 拷贝成功

从9的位置开始拷贝:9->4  8->3  7->2  6->1  5->0 拷贝成功

情况4:

	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };memmove(arr + 5, arr, 20);

从0的位置开始拷贝:0->5  1->6  2->7  3->8  4->9 拷贝成功

从4的位置开始拷贝:4->9  3->8  2->7  1->6  0->5 拷贝成功

我们可以根据这些情况得出结论:

如果dest在src左边,就从首元素开始拷贝;如果dest在src右边,就从末尾元素开始拷贝

代码实现如下:

void* my_memmove(void* dest, void* src, int num)
{void* ret = dest;  assert(dest && src); //判断目标地址是否小于或等于源地址  //如果是,则按照从前往后的顺序复制,防止覆盖还未复制的源数据  if (dest <= src){//从前往后复制数据  while (num--) //循环num次,每次复制一个字节  {*(char*)dest = *(char*)src; //将src指向的字节复制到dest指向的位置  dest = (char*)dest + 1; // 将dest指针向后移动一个字节  src = (char*)src + 1; // 将src指针向后移动一个字节  }}else{// 如果目标地址大于源地址,则按照从后往前的顺序复制  // 这样可以避免在复制过程中覆盖还未读取的源数据  dest = (char*)dest + num - 1; // 将dest指针移动到目标区域的最后一个要复制的字节  src = (char*)src + num - 1; // 将src指针移动到源区域的最后一个要复制的字节  while (num--) {*(char*)dest = *(char*)src; //将src指向的字节复制到dest指向的位置  dest = (char*)dest - 1; //将dest指针向前移动一个字节  src = (char*)src - 1; //将src指针向前移动一个字节  }}return ret; 
}
int main()
{int arr[] = { 0,1,2,3,4,5,6,7,8,9 };my_memmove(arr + 2, arr, 20);for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

memset

1.memset的用法

memset用于将某一块内存中的内容全部设置为指定的值

函数原型为:

void * memset ( void * ptr, int value, size_t num );

其中,其参数的含义如下:

ptr:指向要设置的内存区域的指针

value:要设置的值,该值被转换为 unsigned char 并复制到目标内存区域

num:要设置的字节数

返回值

返回指向被设置内存区域的起始位置的指针

注意:

memset 是按字节操作的,因此当你对非字符类型(如整数、浮点数等)的数组使用 memset 时,需要特别小心。确保你了解目标数组的类型,并知道如何正确地初始化它。对于非零初始化,特别是非字符类型,memset 可能不会按预期工作。

2.memset的使用

int main()
{char arr[] = "hello world";memset(arr, 'x', 5); //将前5个字符替换为'x'printf("%s\n", arr); //打印修改后的字符串return 0;
}

输出结果为:

xxxxx world

错误演示:

int main()
{int arr[5] = { 1,2,3,4,5 };memset(arr, 1, sizeof(arr));int i = 0;for (i = 0; i < 5; i++){printf("%d ", arr[i]);}return 0;
}

输出结果为:

16843009 16843009 16843009 16843009 16843009

memset设置是以字节为单位,容易造成不符合我们预期的情况

3.memset的模拟实现

思路:memset的模拟实现和strncpy有点相似,但memset需要将数据类型强制转换为(char*)

代码如下:

void* my_memset(void* str, int c, size_t n)
{assert(str);// 将void*类型的指针转换为char*类型的指针,以便按字节访问  char* tmp = (char*)str;// 使用while循环遍历n个字节  while (n--){// 将当前字节设置为字符c  *tmp = (char)c;// 移动到下一个字节  tmp++;}return str;
}int main()
{char str[] = "hello world";my_memset(str, 'x', 6);printf("%s\n", str);return 0;
}

memcmp

1.memcmp的用法

memcmp用于比较内存区域的内容

函数原型为:

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

其中,其参数的含义如下:

s1 和 s2 是指向要比较的内存区域的指针

n 是要比较的字节数

返回值:

返回值:

如果返回值 < 0,则表示 ptr1 小于 ptr2

如果返回值 > 0,则表示 ptr1 大于 ptr2

如果返回值 = 0,则表示 ptr1 等于 ptr2

注意:

memcmp 函数按字节比较两个内存区域的内容,而不是按数据类型或元素进行比较。因此,它对于比较任意类型的内存区域都是有效的,只要你知道要比较的字节数

2.memcmp的使用

int main()
{int arr1[] = { 0,1,2,3,4,5,5,5 };int arr2[] = { 0,1,2,3,4,6,6,6 };int ret = memcmp(arr1, arr2, 21);printf("%d\n", ret);return 0;
}

输出结果为:

-1

3.memcmp的模拟实现

思路:memcmp的模拟实现与strncmp差不多,只是也需要先强制类型转换

代码如下:

int my_memcmp(const void* str1, const void* str2, size_t n)
{assert(str1 && str2);char* p1 = (char*)str1;char* p2 = (char*)str2;while (n-- && (*p1 == *p2)){p1++;p2++;}return *p1 - *p2;
}int main()
{int arr1[] = { 0,1,2,3,4,3,6,7 };int arr2[] = { 0,1,2,3,4,2,4,4 };int ret = my_memcmp(arr1, arr2, 24);printf("%d\n", ret);char str1[] = "abcddd";char str2[] = "abcdef";ret = my_memcmp(str1, str2, 5);printf("%d\n", ret);return 0;
}

输出结果为:

1
-1

结束语

希望看完这篇文章能对友友们有所帮助!!!

点赞收藏关注!!!

谢谢各位!!!

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

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

相关文章

MySQL生产环境常见故障及解决方案汇总

MySQL生产环境常见故障及解决方案汇总 1. MySQL主从同步异常故障1.1. 情景说明1.2. 排查过程1.3. 数据同步2. MySQL慢查询故障1. MySQL主从同步异常故障 1.1. 情景说明 MySQL主库网卡需要更换IP地址,并将原IP地址配置为MySQL集群的VIP地址,上层应用程序其实不需要更改连接My…

docker版Elasticsearch安装,ik分词器安装,用户名密码配置,kibana安装

1、安装es和ik分词器 创建映射目录并赋予权限&#xff1a; mkdir -p /docker_data/elasticsearch/conf mkdir -p /docker_data/elasticsearch/data mkdir -p /docker_data/elasticsearch/plugins chmod -R 777 /docker_data/elasticsearch编写配置文件&#xff1a; vi /dock…

Spring拓展点之SmartLifecycle如何感知容器启动和关闭

Spring为我们提供了拓展点感知容器的启动与关闭&#xff0c;从而使我们可以在容器启动或者关闭之时进行定制的操作。Spring提供了Lifecycle上层接口&#xff0c;这个接口只有两个方法start和stop两个方法&#xff0c;但是这个接口并不是直接提供给开发者做拓展点&#xff0c;而…

1379. 找出克隆二叉树中的相同节点

说在前面 &#x1f388;不知道大家对于算法的学习是一个怎样的心态呢&#xff1f;为了面试还是因为兴趣&#xff1f;不管是出于什么原因&#xff0c;算法学习需要持续保持。 题目描述 给你两棵二叉树&#xff0c;原始树 original 和克隆树 cloned&#xff0c;以及一个位于原始…

大模型量化技术-AWQ

大模型量化技术-AWQ 在2023年6月,Ji Lin等人发表了论文AWQ:Activation-aware Weight Quantization for LLM Compression and Acceleration。 这篇论文详细介绍了一种激活感知权重量化算法,可以用于压缩任何基于 Transformer 的语言模型,同时只有微小的性能下降。关于 AWQ 算…

Golang和Java的对决:从设计理念到工具链的全面比较

文章目录 使用率排名Golang和Java设计理念语法和类型系统并发处理资源消耗生态系统和工具链 结语 使用率排名 据最新的2024年3月 Tiobe 编程语言排行榜&#xff0c;目前 Golang 的使用率排名为第8呈上升趋势&#xff0c;Java 的使用率排名为第4呈下降趋势 2024年3月2023年3月…

Python搭建编程环境—安装Python3解释器

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;零基础学Python &#x1f4ac;个人格言&#xff1a;不断的翻越一…

深入浅出,解析什么是网络切片

一、网络切片诞生背景&#xff1f; 网络切片的需求来自于业务对网络提出的差异化要求&#xff0c;要求一张物理网络上对不同的业务进行差异化的保障。 网络切片是5G核心网最重要的技术之一&#xff0c;也是网络即服务的直接体现&#xff0c;网络切片本身就是产品和服务。 二…

C++重载和模板

重载与模板 函数模板可以被另一个模板或一个普通非模板函数重载。 与往常一样&#xff0c;名字相同的函数必须具有不同数量或类型的参数。 如果涉及函数模板&#xff0c;则函数匹配规则会在以下几方面受到影响&#xff1a; 对于一个调用&#xff0c;其候选函数包括所有模板…

SAP:无法在插件模式 HTTP 中处理消息 E ** xxx

问题描述&#xff1a;利用post方式接口&#xff0c;返回信息为 无法在插件模式 HTTP 中处理消息 E ** xxx ,如何排查是因为什么问题导致的&#xff1f; 解决方法&#xff1a; 事务码&#xff1a;SE91, 输入消息类&#xff0c;消息编号&#xff0c;点击显示&#xff0c;查看报…

Sketch webView方式插件开发技术总结

Sketch作为一款广受欢迎的矢量图形设计工具&#xff0c;其功能远不止基础的矢量设计&#xff0c;它的真正实力部分源自其丰富的插件生态系统。Sketch向开发者提供了官方的第三方插件接口&#xff0c;这使得整个社区能够创建和分享众多功能各异的插件&#xff0c;极大地拓展了Sk…

vue3+ts 调用接口,数据显示

数据展示 &#xff08;例&#xff1a;展示医院等级数据&#xff0c;展示医院区域数据同理。&#xff09; 接口文档中&#xff0c;输入参数 测试一下接口&#xff0c;发请求 看是否能够拿到信息 获取接口&#xff0c;api/index.ts 中 /home/index.ts // 统一管理首页模块接口 i…