当前位置: 首页 > news >正文

C语言第十章内存函数

一. memcpy函数的使用和模拟实现

1.函数的使用

        该函数和上一章节学到的strcpy比较相似,本章节是通过内存来实现数据的复制,而上章节的strcpy函数主要是通过数据本身实现复制操作。

        根据上述图片可以得到,该函数的函数参数为void *类型。实现了泛型编程。可以接收不同类型的数据。函数返回值为void*类型,强制类型转换之后就可以得到目标类型的函数返回值了。

        函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。这个函数在遇到 '\0' 的时候并不会停下来,因为该函数本质上使用的是内存进行数据的复制,而不是读取内容从而实现数据的复制。如果source和destination有任何的重叠,就不可以达到预期效果,复制的结果都是未定义的(具体详见模拟实现的说模拟说明)。

        下面举一个该函数使用的例子:

#include <stdio.h>
#include <string.h>    //该函数需要的头文件
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };    //创建两个整型数组,用于接下来的内存复制int arr2[10] = { 0 };memcpy(arr2, arr1, 20);    //调用函数,将从arr1开始,复制20个字节的数据给arr2int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr2[i]);    //通过循环进行数组的打印,查看复制效果}return 0;
}

        上述是函数的使用代码,首先创建了两个整型数组,一个是接收复制内容的目标空间;另一个是提供复制内容的源头数组。调用函数时,第三个参数表示的是复制内容的大小(单位是字节)。所以上述代码可以实现:将arr1的前20个字节的内容复制到arr2数组中去。

2.函数的模拟实现

void * memcpy ( void * dst, const void * src, size_t count)
{void * ret = dst;        //保存首地址,方便后续处理返回值assert(dst);            //防止接收的函数参数指针为空指针,避免影响解引用操作assert(src);while (count--) {*(char *)dst = *(char *)src;    //将地址先进行强制类型转换后解引用,最后进行数据的复制dst = (char *)dst + 1;    
//将指针后移,因为强制类型转换操作是暂时性的,当强转之后进行后置++,此时强制类型转换过了时效性,就会产生对 void*指针解引用的错误操作。所以采用简单的+1计算操作src = (char *)src + 1;}return(ret);
}

        根据上方模拟实现函数的代码和图片解释可得,函数需要以字节为单位复制数据,这时候选择强制类型转化为char*,原因是:char*的大小为1个字节,刚好为函数参数的最小单位。这样无论碰到什么类型的数据,都会逐一字节进行复制,实现了泛型编程。函数在实现过程中,通过控制while循环的循环条件,从而控制复制内容字节数的大小。

        指针后移不用简便的后置++,原因是:C语言的强制类型转化操作具有暂时性,当进行到自增运算时,强制类型转换就会过了时效性,导致代码解引用void*类型的指针,这种行为是具有危险性的,所以这里的模拟实现采用了简单的+1操作。

二.memmove函数使用和模拟实现

1.函数的使用

        讲到该函数,貌似这个函数就是为了弥补memcpy函数的不足的(不能复制重叠的数据)。该函数可以将一段内存数据从源地址复制到目标地址处。

         通过上述图片可得,函数的返回类型和每个参数和memcpy函数极为相似,实际上目的也是相同的。都实现了泛型编程。

         memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用memmove函数进行数据内容的复制。

        下面举一个该函数的使用例子:

#include <stdio.h>
#include <string.h>    //该函数需要的头文件
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };        //创建一个数组,尝试自己给自己复制数据(重叠)memmove(arr1+2, arr1, 20);    //调用函数,将arr1的前20字节数据复制到arr1+2空间处int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr1[i]);        //利用循环打印该数组,查看函数调用的效果}return 0;
}

        输出:1 2 1 2 3 4 5 8 9 10

        上述代码的理解:上述代码通过自己复制数据给自己,来实现重叠数据复制时,函数效果的验证。根据输出结果可以得出,该函数确实具有复制重叠数据内容的能力。

2.函数的模拟实现

void * memmove ( void * dst, const void * src, size_t count)
{void * ret = dst;    //保存首地址,用于最后的函数返回if (dst <= src || (char *)dst >= ((char *)src + count))//当已拷贝数据不会对未拷贝数据进行覆盖时{while (count--) //进行正向复制{*(char *)dst = *(char *)src;dst = (char *)dst + 1;src = (char *)src + 1;}}else     //否则进行反向复制{dst = (char *)dst + count - 1;src = (char *)src + count - 1;while (count--) {*(char *)dst = *(char *)src;dst = (char *)dst - 1;src = (char *)src - 1;}}return(ret);
}

        该函数的实现比较复杂,需要分情况讨论。因为函数需要解决重叠空间复制数据的问题,所以根据上方图片可以看出思路(可以根据目标空间和源头数据的位置关系来决定复制的方向)。

        当(dst <= src || (char *)dst >= ((char *)src + count)) 时,说明已拷贝数据不会对未拷贝数据进行覆盖,所以使用正向复制的顺序;当dst位于src和src+count中间时,说明已拷贝数据会对未拷贝数据进行覆盖,所以进行反向复制。

三.memset函数的使用

        该函数的作用是:改变一段内存区域的值,将其变为我们想改变的值。

        通过上述图片可以得知,该函数的函数参数为void*类型,原因同上,均为达到泛型编程的目的。第二个函数参数为改变后的值。第三个参数为要填充的字节数。最后函数返回类型为void*类型返回的是数据的首地址。

        这里需要注意的是,该函数填充内容时,是以字节为单位的,填充过程中,会将每个字节都填充为所需值。系统存数据是以二进制的补码进行存储的,如果填充的是大于1个字节的类型,那么一般不会达到预期的效果。

        比如:将一个空间的数据(整型值2,补码为:00000000 00000000 00000000 00000010),全部变为整型值1,这时候用此函数就会将该地址存储的内容,每个字节都变为1,数据就会变成00000001 00000001 00000001 00000001,可以明显看出得到的数据并不是整型值1,原因是整型值1的存储并不是每个字节都是1。

        下面举一个该函数的使用例子:

#include <stdio.h>
#include <string.h>    //该函数需要的头文件
int main ()
{char str[] = "hello world";    //定义字符数组,方便后续的函数验证memset (str,'x',6);    //调用函数,将str数组的前6个字节全部变为xprintf(str);    //打印字符串str,查看函数的具体效果return 0;
}

输出:xxxxxx world

四.memcmp函数的使用

        该函数的作用是内存比较,用于比较两个内存区域的前num字节内容大小。

        根据上方图片可以看出:前两个函数参数为void *指针(待比较的两个内存区域),旨在接收任意类型的数据,增强函数的健壮性,实现泛型编程。第三个函数参数为num,用于控制比较内容的多少(单位是字节)。

        该函数和strcmp函数比较相似,但是还是有些许区别。该函数不会以 \0为终止条件,不会检查内容的结束符。该函数的返回值和strcmp相同:第一个函数参数大时,函数返回大于0 的数字;第二个函数参数大时,函数返回小于0的数字;两个函数参数一样大时,返回数字0。

        下面是该函数的使用举例:

#include <stdio.h>
#include <string.h>    //该函数需要的头文件
int main()
{char buffer1[] = "DWgaOtP12df0";        //创建两个字符数组,用于接下来的函数验证char buffer2[] = "DWGAOTP12DF0";int n;n = memcmp(buffer1, buffer2, sizeof(buffer1)); //调用函数比较两字符串的前sizeof(buffer1)字节内容if (n > 0)         //根据判断结果打印相应的内容printf("'%s' is greater than '%s'.\n", buffer1, buffer2);else if (n < 0) printf("'%s' is less than '%s'.\n", buffer1, buffer2);else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);return 0;
}

        上述代码,通过调用该函数判断,来判断两内存区域数据内容的大小,这和strcmp函数类似,均是逐个字节进行ASCII码值大小的比较,比较的并不是内容的长短。

http://www.lryc.cn/news/626515.html

相关文章:

  • python numpy.random的基础教程(附opencv 图片转数组、数组转图片)
  • Dog Tricks
  • vue3项目,main.ts中设置router,在各个页面上还用引用vue-router吗
  • 性能测试报告深度解析:从冰冷数据到火热洞察
  • Flink学习
  • 详解flink java table api基础(三)
  • 2.3 Flink的核心概念解析
  • 24V降12V电源芯片WD5030,电路设计
  • linux 内核 - 内存管理单元(MMU)与地址翻译(一)
  • Flink Stream API - 顶层Operator接口StreamOperator源码超详细讲解
  • 软件测试中,JMeter 的作用以及优缺点是什么?
  • 【报错】Please do not run this script with sudo bash
  • three.js学习记录(第四节:材质外观)
  • Git 新手完全指南(二):在vscode中使用git
  • 【图像算法 - 19】慧眼识苗:基于深度学习与OpenCV的大棚农作物生长情况智能识别检测系统
  • PostgreSQL 中的金钱计算处理
  • K8S-Secret资源对象
  • 从零开始学AI——13
  • 机器学习(Machine Learning, ML)
  • mysql数据恢复
  • iOS App 上架实战 从内测到应用商店发布的全周期流程解析
  • QT聊天项目DAY20
  • java17学习笔记
  • 【Tech Arch】Apache HBase分布式 NoSQL 数据库
  • idea maven 设置代理
  • FastAPI初学
  • 《深度解析PerformanceObserverAPI: 精准捕获FID与CLS的底层逻辑与实践指南》
  • 【STM32】HAL库中的实现(六):DAC (数模转换)
  • 调用海康威视AI开放平台接口实现人体关键点检测
  • Java毕业设计选题推荐 |基于SpringBoot+Vue的知识产权管理系统设计与实现