C语言(内存函数)

        Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,欢迎欢迎~~     

                                                💥个人主页:小羊在奋斗

                                                💥所属专栏:C语言   

        本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为一些学友们展示一下我的学习过程及理解。文笔、排版拙劣,望见谅。 

                                1、memcpy 的使用和模拟实现

                                2、memmove 的使用和模拟实现

                                3、memset 函数的使用

                                4、memcmp 函数的使用

1、memcpy 的使用和模拟实现

        1.1  memcpy 函数的使用 

        memcpy 前面的 mem 指的是 memmory ,英文单词“记忆”,在C语言中指的是内存。后面要介绍的 memmove、memset 和 memcmp 都是如此。

        memcpy 是一个内存拷贝函数,其作用是将一个内存区域内指定的 count 个字节大小的内容拷贝到目标内存空间内。值得注意的是,虽然 memcpy 是一个内存函数,但其是定义在 <string.h>头文件内的。

         上面关于 memcpy 函数的作用及其用法的描述还是很好理解的,这里再做一些说明。

        (1)为了使 memcpy 函数可以实现对任意类型的内容拷贝,其参数定义为了 void *类型的指针;

        (2)跟之前学过的字符串相关的函数一样,memcpy 函数拷贝的目标空间必须是可修改的,而被拷贝的内存区域可用 const 修饰;

        (3)当 source 和 destination 有任何重叠的时候,复制的结果都是未定义的,也就是说memcpy 函数不负责重叠内存的拷贝。

        这个函数还是比较简单的。

       1.2  memcpy 函数的模拟实现

        memcpy 函数和 strcpy 函数有相似的地方,所以其实现的逻辑也就应该和 strcpy 函数的模拟实现类似。首先我们需要搞清楚,为了实现对任意类型的内容拷贝,我们将 memcpy 函数的参数及其返回值都设定成了 void * 类型的指针,但是 void * 类型的指针有缺点,不能直接进行解引用,也不能对其进行 +- 操作,那我们就要想一个办法解决这个问题。

        其实这个问题我们之前在模拟实现 qsort 函数的时候就有了一个解决办法,就是将其强转为char * 类型的指针,因为 char * 类型的指针指向的对象大小是一个字节,是所有类型中字节大小最小的,不管对象类型是多大字节,只要一个字节一个字节地拷贝,就可以对实现对任意类型的内容拷贝了。

        那有了上面的思路,我们就能写出下面的代码:

#include <stdio.h>
#include <assert.h>void* my_memcpy(void* dest, const void* sour, size_t count)
{assert(dest && sour);void* pd = dest;while (count--)//控制拷贝多少个字节{*(char*)dest = *(char*)sour;((char*)dest)++;((char*)sour)++;}return pd;//返回目标空间的起始地址
}void text1()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[20] = { 0 };int* pi = my_memcpy(arr2, arr1 + 2, 20);for (int i = 0; i < 5; i++){printf("%d ", *(pi + i));}printf("\n");
}void text2()
{char str1[] = "abcdefghijklmnopqrstuvwxyz";char str2[20] = { 0 };char* ps = my_memcpy(str2, str1 + 5, 10);printf("%s\n", ps);
}int main()
{text1();text2();return 0;
}

        跟之前我们模拟实现 qsort 函数相比,这就是张飞吃豆芽。 

        上面我们是将一个内存区域的内容拷贝到另一个内存区域,那能不能实现在一个内存区域内的拷贝呢?我们来试一下:

        可以看到,当我们在一个内存区域内拷贝,并且内存有重叠的时候,my_memcpy 函数就不能完成我们想要的结果了,这是因为重叠的部分已经被拷贝过来的内容代替,原内容就消失了,当拷贝到重叠的内存区域时,拷贝的还是之前拷贝过来的内容。不过只要内存不重叠,在一个内存区域内拷贝也是可行的。

        但是,我们可以看到 memcpy 函数并没有这个问题,那是我们模拟的函数有问题吗?其实不是的,memcpy 函数之所以没有这个问题,是因为在某些系统上,memcpy函数可能会检测是否源内存和目标内存有重叠,并采取一些措施以确保正确的结果。然而,这种行为是不可靠的,不同的编译器或系统的实现方式可能会导致不同的结果。

         上面的情况和我们在 字符、字符串函数 中介绍到的用 strcat 函数实现一个字符串自己拼接到自己末尾产生的问题是类似的,同样的 strcat 函数表面上虽然也没有什么问题,但是这种行为也是不可靠的。为了代码的可移植性和安全性,最好还是使用memmove 函数来处理重叠内存的情况。接下来我们就来介绍 memmove 函数。 

2、memmove 的使用和模拟实现

        2.1 memmove 函数的使用

        对比 memcpy 函数,memmove 函数与之是及其相似的,特别的是 memmove 函数操作的对象是可以重叠的,正如它所描述的它会将内容如同先复制到一个临时数组中,这样就解决了目标内存区域的内容被覆盖的问题。 

        2.1 memmove 函数的模拟实现 

        那么了解了 memmove 函数的逻辑,模拟实现它也不是什么难事。我们只需要创建一个临时数组过渡就行,于是就得到了下面的代码:

#include <stdio.h>
#include <assert.h>void* my_memmove(void* dest, const void* sour, size_t count)
{assert(dest && sour);void* pd = dest;int i = 0;char arr[1000] = { 0 };for (i = 0; i < count; i++){arr[i] = *(char*)sour;((char*)sour)++;}for (i = 0; i < count; i++){*(char*)dest = arr[i];((char*)dest)++;}return pd;
}int main()
{int i = 0;int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr1) / sizeof(arr1[0]);my_memmove(arr1 + 2, arr1, 20);for (i = 0; i < sz; i++){printf("%d ", arr1[i]);}return 0;
}

        是不是很简单呢,这样我们就实现了模拟 memmove 函数的功能。但是上面这种创建临时字符数组的办法有一点不足,因为我们并不能确定被拷贝的内容有多大,所以只能模糊地创建一个比较大的数组,但是这个比较大是多大没办法知道,创建大了浪费,创建小了不够,那有没有什么办法能解决这个问题呢? 

        2.3 memmove 函数的模拟优化

        既然我们并不能确定要创建一个多大的临时数组,那我们干脆放弃创建临时数组的方法另辟奇径。

        让我们再回到之前遇到的问题,如果内存重叠时拷贝会将原内容覆盖。那是不是我们拷贝的方法有问题呢?来看:

        将红色方框内的内容拷贝到蓝色方框内:

 

        我们发现,从前向后拷贝行不通,因为会覆盖掉还没拷贝的内容;但从后向前拷贝是可行的,并没有出现还没拷贝的内容被覆盖的情况。

        将蓝色方框内的内容拷贝到红色方框内:

        我们又发现,从后向前行不通,但从前向后是可行的。

        而之所以有时需要从前向后拷贝,有时需要从后向前拷贝,是取决于是将前面的内容拷贝到后面,还是将后面的内容拷贝到前面。

        前面介绍数组的时候我们说过,数组元素随着下标的增大地址逐渐增大。也就是说,如果上面需要将红色方框内的内容拷贝到蓝色方框内,那么当指针p1小于指针p2时,需要从后向前拷贝;当指针p1大于指针p2时,需要从前向后拷贝。而当两个内存区域没有重叠时,从前向后和从后向前都是可行的。

        那么,我们就可以在拷贝之前先比较一下指针dest和指针sour的大小,然后再选择是从前向后拷贝还是从后向前拷贝。

#include <stdio.h>
#include <assert.h>void* my_memmove(void* dest, const void* sour, size_t count)
{void* pd = dest;assert(dest && sour);if (dest < sour)//从前向后{while (count--){*(char*)dest = *(char*)sour;((char*)dest)++;((char*)sour)++;}}else//从后向前{while (count--){*((char*)dest + count) = *((char*)sour + count);}}return pd;
}void text1()
{int i = 0;int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr1) / sizeof(arr1[0]);my_memmove(arr1 + 2, arr1, 20);for (i = 0; i < sz; i++){printf("%d ", arr1[i]);}
}void text2()
{int i = 0;int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr1) / sizeof(arr1[0]);my_memmove(arr1, arr1 + 2, 20);for (i = 0; i < sz; i++){printf("%d ", arr1[i]);}
}void text3()
{int i = 0;char str1[] = "abcdefghijklmn";int sz = sizeof(str1) / sizeof(str1[0]);my_memmove(str1, str1 + 2, 5);printf("%s\n", str1);
}void text4()
{int i = 0;char str1[] = "abcdefghijklmn";int sz = sizeof(str1) / sizeof(str1[0]);my_memmove(str1 + 2, str1, 5);printf("%s\n", str1);
}int main()
{//text1();//text2();//text3():text4();return 0;
}

        这时候我们写的 my_memmove 函数就比较完善了。

        其实小伙伴们也能感觉到 memmove 函数完全可以代替 memcpy 函数,而且 memmove 函数不用管内存是否重叠的问题。那 memcpy 函数不就没有存在的必要了吗?其实内存重叠只是一种特殊情况,在确定没有内存重叠的情况下使用 memcpy 函数效率会更高,因为 memcpy 函数没有比较指针大小这一步骤。

        当然如果你嫌麻烦始终使用 memmove 函数也是没有什么问题的,就是效率低那么一丢丢而已。

3、memset 函数的使用

        memset 函数是用来设置内存的,它的作用是将内存中的值以字节为单位设置成想要的内容。 

        需要注意的是,memset 函数是以字节为单位设置的,否则会写成下面这种代码:

        我们知道整型占4个字节,整数7以16进制表示为:0x07 00 00 00,上面的代码执行过后就变成了:0x01 01 01 01,并没有达到我们想要的效果。所以我们要谨记 memset 函数是以字节为单位一个字节一个字节设置的,并不是以元素为单位的。

4、memcmp 函数的使用

        memcmp 函数和 strncmp 函数极其相似,也是比较两个指针指向内容的大小,唯一的区别是 strncmp 只能比较字符串,而 memcmp 可以比较任意类型。和 memset 函数一样 memcmp 也是以字节为单位比较的。 

  

        以上所有的函数都是可以操作内存的函数,与前面介绍的字符、字符串函数不同的是内存函数可以操作任意类型的内容。 

          如果觉得我的文章还不错,请点赞、收藏 + 关注支持一下,我会持续更新更好的文章。  

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

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

相关文章

springboot+vue+mybatis博物馆售票系统+PPT+论文+讲解+售后

如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统博物馆售票系统信息管理难度大&#xff0c;容错率低&#xff0c;…

sqoop操作

介绍 sqoop是隶属于Apache旗下的, 最早是属于cloudera公司的,是一个用户进行数据的导入导出的工具, 主要是将关系型的数据库(MySQL, oracle...)导入到hadoop生态圈(HDFS,HIVE,Hbase...) , 以及将hadoop生态圈数据导出到关系型数据库中 操作 将数据从mysql中导入到HDFS中 1.全量…

Pytest框架中的Setup和Teardown功能

在 pytest 测试框架中&#xff0c;setup 和 teardown是用于在每个测试函数之前和之后执行设置和清理的动作&#xff0c;而pytest 实际上并没有内置的 setup 和 teardown 函数&#xff0c;而是使用了一些装饰器或钩子函数来实现类似的功能。 学习目录 钩子函数&#xff08;Hook…

SAP PP学习笔记14 - MTS(Make-to-Stock) 按库存生产(策略10),以及生产计划的概要

上面讲了SAP里面的基础知识&#xff0c;BOM&#xff0c;作业手顺&#xff08;工艺路线&#xff09;&#xff0c;作业区&#xff08;工作中心&#xff09;&#xff0c;MRP&#xff0c;MPS等概念&#xff0c;现在该到用的时候了。 SAP PP学习笔记07 - 简单BOM&#xff0c;派生BO…

STC8增强型单片机进阶开发--独立按键

知不足而奋进 望远山而前行 文章目录 目录 文章目录 前言 目标 内容 原理图 按键消抖 软件设计 要求 分析 实现单个按钮 实现多个按钮 使用位操作存储状态 总结 前言 本次学习旨在探索按键操作及按键消抖的原理和实现方法。通过学习原理图、按键消抖的三种方法以及软件设计的要…

如何选择D类音频放大器(数字功率放大器)

1 简介 多年来&#xff0c;音频内容一直在不断发展。从当地唱片店购买 12 英寸 LP 黑胶唱片的时代已经成为过去&#xff0c;现在我们通过流式传输几乎可即时播放云端的任何内容。虽然一些音频爱好者会为了获得新奇体验而重拾黑胶唱片&#xff0c;但今天绝大多数的音频都是以数…

JVM学习笔记(持续更新)

JDK、JRE、JVM区别&#xff1f; 类加载过程 装载 验证 准备 解析 初始化 类加载器分类 双亲委派模型 如何打破双亲委派模型&#xff1f; 自定义类加载器&#xff0c;集成ClassLoader类重写loadClass,如Tomcat JVM内存模型 JVM 需要使用计算机的内存&#xff0c;Java 程序…

【LeetCode 101】对称二叉树

1. 题目 2. 分析 这道题比较经典。我又一次做错了&#xff0c;这次是花了20min都没有做出来。 最开始我的思想就是&#xff0c;递归比较左根节点的左子树和右根节点的右子树是否对称即可&#xff0c;然后觉得能解决问题了&#xff0c;便动手coding。哪知道&#xff0c;又碰到了…

电源滤波器怎么选用

电源滤波器怎么选用 滤波器应用场景及作用第一步&#xff1a;第二步&#xff1a;第三步&#xff1a;第四步&#xff1a; 滤波器应用场景及作用 可以有效解决EMC测试无法通过、端口防护、滤除干扰、设备保护等问题 主要功能有: 1、降低主电源谐波; 2、保护驱动装置电力电子元件…

算法人生(18):从神经网络的“剪枝策略”看“怎么找回时间”

IT人的工作和生活难平衡这事&#xff0c;到底要怎么解决呢&#xff0c;让我们从神经网络的“剪枝策略”中找点灵感吧&#xff01; 剪枝策略是指训练和优化深度神经网络时采取的一种技术&#xff0c;从名字就知道&#xff0c;它就像修剪树木一样&#xff0c;去除不必要的枝叶&a…

Vuex 是什么?VueX简介

聚沙成塔每天进步一点点 本文内容 ⭐ 专栏简介Vuex 是什么核心概念1.State&#xff08;状态&#xff09;2. Getter&#xff08;获取器&#xff09;3. Mutation&#xff08;突变&#xff09;4. Action&#xff08;动作&#xff09;5. Module&#xff08;模块&#xff09; 原理解…

使用STS临时访问凭证通过客户端直连OSS对象存储服务器

目录 1、导论 2、客户端直传 3、创建RAM用户以及RAM角色 4、如何实现客户端直传 4.1、跨域访问 4.2、安全授权 5、代码示例 5.1、后端代码实例 5.2、客户端代码实例 1、导论 最近在做项目的过程中使用到了阿里云OSS来存储客户端上传的文件&#xff0c;方法是直接将客…

Keras深度学习框架实战(3):EfficientNet实现stanford dog分类

1、通过EfficientNet进行微调以实现图像分类概述 通过EfficientNet进行微调以实现图像分类&#xff0c;是一个使用EfficientNet作为预训练模型&#xff0c;并通过微调&#xff08;fine-tuning&#xff09;来适应特定图像分类任务的过程。一下是对相关重要术语的解释。 Effici…

Flutter-自定义可展开文本控件

Flutter 在移动开发中&#xff0c;常常需要处理一些长文本显示的场景&#xff0c;如何优雅地展示这些文本并允许用户展开和收起是一个常见的需求。在本文中&#xff0c;我将分享如何使用Flutter实现一个可展开和收起的文本控件。 效果 我们将实现一个可展开和收起的文本控件…

yolov10模块

yolov10模块 1 C2f2 C2fCIB2.1 CIB2.2 RepVGGDW 3 PSA4 SCDown5 v10Detect 论文代码&#xff1a;https://github.com/THU-MIG/yolov10 论文链接&#xff1a;https://arxiv.org/abs/2405.14458 Conv是Conv2dBNSiLU PW是Pointwise Convolution(逐点卷积) DW是Depthwise Convolut…

【SQL学习进阶】从入门到高级应用【企业真题】

文章目录 第一题第二题第三题第四题第五题第六题第七题第八题第九题MySQL行转列使用case whengroup by完成 第十题 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f495;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01; &#x1f495;希望您在这…

疫情物资捐赠和分配系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;机构管理&#xff0c;用户管理&#xff0c;发放管理&#xff0c;物资管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;物资论坛&#xff0c;公告信息…

STM32作业设计

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

彻底卸载Windows Defender

概述 卸载Windows Defender的方法有很多&#xff0c;如修改注册表、组策略&#xff0c;执行脚本等等&#xff0c;这些方法操作过于繁琐和复杂&#xff0c;不适合小白&#xff0c;今天带来一款强大的卸载工具&#xff0c;只需要以管理员身份运行该软件即可&#xff0c;不用其他操…

禹晶、肖创柏、廖庆敏《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》Chapter 6插图

禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 Chapter 6插图