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

模拟实现memmove,memcpy,memset

目录

前言

一、模拟实现memmove

代码演示:

二、模拟实现memcpy

代码演示:

三、模拟实现memset

代码演示:

总结


前言

这篇文章主要讲解了库函数的模拟实现,包含memmove,memcpy,memset


一、模拟实现memmove

     memmove函数是C语言标准库中的一个函数,用于从一个内存位置复制数据到另一个内存位置。这个函数在处理重叠内存区域时特别有用,因为它能够在源区域被覆盖之前将重叠区域的字节拷贝到目标区域中。如果没有重叠,memmove的行为与memcpy相同。

函数声明和参数

memmove函数的声明如下:

void *memmove(void *str1, const void *str2, size_t n)

其中,参数str1是指向目标数组的指针,str2是指向数据源的指针,而n是要复制的字节数。

返回值

函数返回一个指向目标存储区str1的指针。

memmove函数的实现关键在于如何处理内存重叠的情况。如果源地址和目标地址重叠,memmove会从后向前复制数据,以避免覆盖还未复制的数据。如果源地址在目标地址之后,它会从前向后复制数据。这种策略确保了数据的正确复制,即使在内存区域重叠的情况下。

代码演示:

#include <stdio.h>void* my_memmove(void* dest, const void* src, size_t n) {unsigned char* d = dest;const unsigned char* s = src;if (d > s) {for (size_t i = n; i > 0; --i) {d[i - 1] = s[i - 1];}} else {for (size_t i = 0; i < n; ++i) {d[i] = s[i];}}return dest;
}int main() {char str1[20] = "Hello, World!";char str2[20] = {0};printf("Before memmove: %s\n", str2);my_memmove(str2, str1, 13);printf("After memmove: %s\n", str2);return 0;
}

      my_memmove 函数的返回值是 void* 类型,通常用于指向目标内存区域的指针。返回 ret 使得调用者可以获得目标内存的起始地址。这在某些情况下是有用的,尤其是在链式调用或需要确认目标地址的情况下。

#include <stdio.h>
#include <string.h>
#include <assert.h>void* my_memmove(void* dest, const 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);}}return ret;
}int main() {int a[] = { 5,5,88,5,22,33,8,8,9,9,33,83,9,8,98 };my_memmove(a+2,a,20);for (int i = 0; i < 15; i++)printf("%d ", a[i]);return 0;
}

二、模拟实现memcpy

      memcpy 是一个用于内存拷贝的函数,其功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中。

其函数原型为:

void *memcpy(void *dest, const void *src, size_t n);

该函数的作用是将源地址 src 指向的内存区域的 n 个字节复制到目标地址 dest 指向的内存区域。

下面是一个基本的 memcpy 实现,按字节进行拷贝:

代码演示:

void *my_memcpy(void *dst, const void *src, size_t n) {if (dst == NULL || src == NULL) {return NULL;}char *pdst = (char *)dst;char *psrc = (char *)src;while (n--) {*pdst++ = *psrc++;}return dst;}

这个实现简单且直接,但没有考虑内存重叠的情况12。

考虑内存重叠

为了处理内存重叠的情况,我们需要判断源地址和目标地址的相对位置。如果目标地址在源地址之后,我们需要从高地址向低地址拷贝;否则,从低地址向高地址拷贝:

void *my_memcpy(void *dst, const void *src, size_t n) {if (dst == NULL || src == NULL) {return NULL;}char *pdst = (char *)dst;char *psrc = (char *)src;if (pdst > psrc && pdst < psrc + n) {pdst += n - 1;psrc += n - 1;while (n--) {*pdst-- = *psrc--;}} else {while (n--) {*pdst++ = *psrc++;}}return dst;}

这个实现考虑了内存重叠的情况,确保数据不会被覆盖13。

优化实现

为了提高拷贝速度,可以按 4 字节(或更大)为单位进行拷贝:

void *my_memcpy(void *dst, const void *src, size_t n) {if (dst == NULL || src == NULL) {return NULL;}int *pdst = (int *)dst;int *psrc = (int *)src;size_t num_ints = n / sizeof(int);size_t num_bytes = n % sizeof(int);if (pdst > psrc && pdst < (int *)((char *)psrc + n)) {pdst += num_ints - 1;psrc += num_ints - 1;while (num_ints--) {*pdst-- = *psrc--;}char *cdst = (char *)pdst;char *csrc = (char *)psrc;cdst += sizeof(int) - 1;csrc += sizeof(int) - 1;while (num_bytes--) {*cdst-- = *csrc--;}} else {while (num_ints--) {*pdst++ = *psrc++;}char *cdst = (char *)pdst;char *csrc = (char *)psrc;while (num_bytes--) {*cdst++ = *csrc++;}}return dst;}

这个实现通过按 4 字节为单位进行拷贝,提高了拷贝速度

#include <stdio.h>
#include <string.h>
#include <assert.h>void* my_memcpy(void* dest, const void* src, size_t num) {if (dest == NULL || src == NULL) {return NULL;}char* a = (char*)dest;char* b = (char*)src;while (num--) {*a++ = *b++;}return dest;
}int main() {int a[] = { 5,5,88,5,22,33,8,8,9,9,33,83,9,8,98 };my_memcpy(a + 2, a, 20);for (int i = 0; i < 15; i++)printf("%d ", a[i]);return 0;
}

三、模拟实现memset

      memset函数是C语言中的一个标准库函数,主要用于给内存块进行初始化操作。它的作用是将某个给定的值填充或复制到指定内存的前n个字节中。这个函数非常适合用于初始化大型结构体或数组。

memset的函数原型如下:

void *memset(void *str, int c, size_t n)

其中,str是指向要填充的内存块的指针,c是要设置的值,通常是一个无符号字符,n是要被设置为该值的字节数。

一个常见的使用场景是初始化一个字符数组或者整型数组。例如,如果想要将一个整型数组的所有元素初始化为-1,可以使用以下代码:

int A[2];memset(A, -1, sizeof A);

这段代码会将数组A的每个元素都设置为-1。

注意事项

        使用memset时需要注意,它按字节对内存块进行初始化。因此,如果你想用它来初始化一个整型数组,除了0和-1之外的其他值可能会导致不符合预期的结果。这是因为memset在赋值时是按字节赋值的,它会将c参数化成二进制之后填入一个字节。例如,如果你尝试使用memset给整型数组赋值100,你可能会得到一个意想不到的结果,因为100的二进制表示会被复制到整型的每个字节中,导致实际的数值并不是100。

memset初始化为无穷大

       在某些算法中,我们可能需要将数组初始化为一个非常大的数,这时可以使用memset来实现。例如:

memset(a, 0x3f, sizeof a);

这段代码会将数组a的每个元素赋值为0x3f3f3f3f,这是一个非常大的数,接近于整型的最大值。

     memset是一个非常有用的函数,它可以快速地初始化内存区域。但是,使用时需要注意它的赋值特性,尤其是在处理非字符类型的数据时。正确理解和使用memset可以避免一些常见的错误,并提高代码的效率和可读性。

代码演示:

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stddef.h>  void* my_memset(void* dest, int value, size_t n) {/* unsigned char* p = (unsigned char*)dest;unsigned char c = (unsigned char)value;size_t align = 4; unsigned int block = c | (c << 8) | (c << 16) | (c << 24);unsigned int* wp = (unsigned int*)p;while (n >= align) {*wp++ = block;n -= align;}p = (unsigned char*)wp;while (n--) {*p++ = c;}return dest;*/unsigned char* p = (unsigned char*)dest;for (size_t i = 0; i < n; i++) {p[i] = (unsigned char)value;}return p;
}
int main() {int a[] = { 5,5,88,5,22,33,8,8,9,9,33,83,9,8,98 };char b[] = "djsjfksjfjsdijfoisjosij";my_memset(b, 'a', sizeof(b));for (int i = 0; i < 15; i++)printf("%c ", b[i]);return 0;
}

在这个例子中,我们定义了一个自定义的myMemset函数来模拟标准库中的memset函数。它接受一个指针ptr,一个要填充的值value,以及要填充的字节数num。函数将指针ptr指向的内存地址开始的连续num字节设置为value

然后,就会发现出现了一堆

      原因是什么呢,传入的val是 2。对于 int 类型的数组,实际上是在每个 int 的 4 个字节中都写入了 2,这会导致 int 的值变成 0x02020202(在小端系统中),而不是简单的 2。

     解决办法


#include <stdio.h>
#include <string.h>
#include <assert.h>void* my_memset(void* ptr, int val, size_t num) {// 计算要设置的元素数量size_t count = num / sizeof(int);int* int_ptr = (int*)ptr;// 设置 int 数组for (size_t i = 0; i < count; i++) {int_ptr[i] = val; // 直接设置为 int 值}// 处理剩余的字节(如果有的话)unsigned char* byte_ptr = (unsigned char*)(int_ptr + count);for (size_t i = 0; i < num % sizeof(int); i++) {byte_ptr[i] = (unsigned char)val; // 设置剩余的字节}return ptr;
}int main() {int a[5] = { 5, 5, 88, 5, 22 }; // 只定义 5 个元素my_memset(a, 2, sizeof(a)); // 设置整个数组为 2for (int i = 0; i < 5; i++) {printf("%d ", a[i]); // 输出应该是 2 2 2 2 2}printf("\n");return 0;
}

但是好像不行

拼尽全力,无法战胜qwq


总结

关于本篇的内容就结束了,对你有帮助的可以点个赞

相关文章:

模拟实现memmove,memcpy,memset

目录 前言 一、模拟实现memmove 代码演示&#xff1a; 二、模拟实现memcpy 代码演示&#xff1a; 三、模拟实现memset 代码演示&#xff1a; 总结 前言 这篇文章主要讲解了库函数的模拟实现&#xff0c;包含memmove&#xff0c;memcpy&#xff0c;memset 一、模拟实现m…...

uni-app 开发安卓 您的应用在运行时,向用户索取(定位、相机、存储)等权限,未同步告知权限申请的使用目的,不符合相关法律法规要求

您的应用在运行时,向用户索取(定位、相机、存储)等权限,未同步告知权限申请的使用目的,不符合相关法律法规要求。 测试步骤:1、 工作台 -打卡,申请定位权限;2、工作台-设置-编辑资料-更换头像,申请相机、存 储权限。 修改建议:APP在申请敏感权限时,应同步说明权限申…...

RHCSA Linux 系统文件内容显示2

6. 过滤文件内容显示 grep &#xff08;1&#xff09;功能&#xff1a;在指定普通文件中查找并显示含指定字符串的行&#xff0c;也可与管道符连用。 &#xff08;2&#xff09;格式&#xff1a;grep 选项... 关键字字符串 文件名... &#xff08;3&#xff09;常用选项及说…...

C语言状态字与库函数详解:概念辨析与应用实践

C语言状态字与库函数详解&#xff1a;概念辨析与应用实践 一、状态字与库函数的核心概念区分 在C语言系统编程中&#xff0c;"状态字"和"库函数"是两个经常被混淆但本质完全不同的概念&#xff0c;理解它们的区别是掌握系统编程的基础。 1. 状态字&…...

【2】Kubernetes 架构总览

Kubernetes 架构总览 主节点与工作节点 主节点 Kubernetes 的主节点&#xff08;Master&#xff09;是组成集群控制平面的关键部分&#xff0c;负责整个集群的调度、状态管理和决策。控制平面由多个核心组件构成&#xff0c;包括&#xff1a; kube-apiserver&#xff1a;集…...

Redis下载

目录 安装包 1、使用.msi方式安装 2.使用zip方式安装【推荐方式】 添加环境变量 配置后台运行 启动&#xff1a; 1.startup.cmd的文件 2.cmd窗口运行 3.linux源码安装 &#xff08;1&#xff09;准备安装环境 &#xff08;2&#xff09;上传安装文件 &#xff08;3&…...

React 文章 分页

删除功能 携带路由参数跳转到新的路由项 const navigate useNavigate() 根据文章ID条件渲染...

OpenCV 图形API(39)图像滤波----同时计算图像在 X 和 Y 方向上的一阶导数函数SobelXY()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::gapi::SobelXY 函数是 OpenCV 的 G-API 模块中用于同时计算图像在 X 和 Y 方向上的一阶导数&#xff08;即 Sobel 边缘检测&#xff09;的一…...

传导发射测试(CE)和传导骚扰抗扰度测试(CS)

传导发射测试(CE)&#xff1a; 测量接收机&#xff1a; 是EMI测试中最常用的基本测试仪器&#xff0c;仪器类型包括准峰值测量接收机、峰值测量接收机、平均值测量接收机和均方根值测量接收机。测量接收机的几个重要指标分别是&#xff1a;6dB处的带宽、充电时间常数、放电时…...

ubuntu 查看现在服务使用的端口

1. 使用netstat命令 netstat是一个常用的网络工具&#xff0c;可以显示网络连接、路由表、接口统计等信息。虽然在较新的系统中netstat可能被ss命令替代&#xff0c;但仍然可以通过安装net-tools包来使用它。 安装net-tools&#xff1a; sudo apt-get install net-tools 查看…...

即插即用模块(1) -MAFM特征融合

(即插即用模块-特征处理部分) 一、(2024) MAFM&MCM 特征融合特征解码 paper&#xff1a;MAGNet: Multi-scale Awareness and Global fusion Network for RGB-D salient object detection 1. 多尺度感知融合模块 (MAFM) 多尺度感知融合模块 (MAFM) 旨在高效融合 RGB 和深度…...

(学习总结34)Linux 库制作与原理

Linux 库制作与原理 库的概念静态库操作归档文件命令 ar静态库制作静态库使用 动态库动态库制作动态库使用与运行搜索路径问题解决方案方案2&#xff1a;建立同名软链接方案3&#xff1a;使用环境变量 LD_LIBRARY_PATH方案4&#xff1a;ldconfig 方案 使用外部库目标文件ELF 文…...

DSP28335入门学习——第一节:工程项目创建

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.20 DSP28335开发板学习——第一节&#xff1a;工程项目创建 前言开发板说明引用解答…...

MDG 实现后端主数据变更后快照自动刷新的相关设置

文章目录 前言实现过程BGRFC期初配置&#xff08;可选&#xff09;设置 MDG快照 BGRFC维护BP出站功能模块 监控 前言 众所周知&#xff0c;在MDG变更请求创建的同时&#xff0c;所有reuse模型实体对应的快照snapshot数据都会记录下来。随后在CR中&#xff0c;用户可以修改这些…...

基于瑞芯微RK3562 的四核 AR M Cortex-A53 + 单核 ARM Cortex-M0——MQTT通信方案

前 言 本文主要介绍创龙科技TL3562-MiniEVM评估板基于MQTT通信协议的开发案例,适用开发...

Java 实体类链式操作

目录 1. 使用返回 this 的 setter 方法 2. 使用 Lombok 的 Accessors 注解 3. 建造者模式 (Builder Pattern) 比较 链式设置参数&#xff08;也称为链式调用或方法链&#xff09;是一种编程风格&#xff0c;可以让代码更加简洁易读。在 Java 实体类中实现链式设置参数通常有…...

【Linux】Linux 操作系统 - 05 , 软件包管理器和 vim 编辑器的使用 !

文章目录 前言一、软件包管理器1 . 软件安装2 . 包管理器3 . Linux 生态 二、软件安装 、卸载三、vim 的使用1 . 什么是 vim ?2 . vim 多模式3 . 命令模式 - 命令4 . 底行模式 - 命令5. 插入模式6 . 替换模式7 . V-BLOCK 模式8 . 技巧补充 总结 前言 本篇笔者将会对软件包管理…...

【操作系统原理05】存储器管理

大纲 文章目录 大纲一. 内存基础知识0.大纲1.什么是内存2.进程运行基本原理2.1 指令工作原理2.2逻辑地址VS物理地址2.3 从写程序到程序运行完整运行三种链接方式 二.内存管理0.大纲1.操作系统进行内存管理 三.覆盖与交换0.大纲1.覆盖技术2.交换技术 四.连续分配管理方式0.大纲1…...

学习笔记—C++—string(练习题)

练习题 仅仅反转字母 917. 仅仅反转字母 - 力扣&#xff08;LeetCode&#xff09; 题目 给你一个字符串 s &#xff0c;根据下述规则反转字符串&#xff1a; 所有非英文字母保留在原有位置。所有英文字母&#xff08;小写或大写&#xff09;位置反转。 返回反转后的 s 。…...

[Swift]Xcode模拟器无法请求http接口问题

1.以前偷懒一直是这样设置 <key>NSAppTransportSecurity</key> <dict><key>NSAllowsArbitraryLoads</key><true/><key>NSAllowsArbitraryLoadsInWebContent</key><true/> </dict> 现在我在Xcode16.3上&#xff…...

返回之术:用 navigate(-1) 闯荡前端江湖

前言 在前端这片江湖,页面跳转宛如轻功水上漂,来去无踪,飘忽不定。但其中有一门绝学,专治“回头是岸”之需求,那便是 React Router 中的 navigate(-1) 身法。 昔日我闯荡项目林,误入“下一页”禁地,一脚踏空,身陷页面迷阵。正当我焦头烂额之际,师父袖袍一挥,口吐一…...

《Operating System Concepts》阅读笔记:p748-p748

《Operating System Concepts》学习第 64 天&#xff0c;p748-p748 总结&#xff0c;总计 1 页。 一、技术总结 1.Transmission Control Protocol(TCP) 重点是要自己能画出其过程&#xff0c;这里就不赘述了。 二、英语总结(生词&#xff1a;3) transfer, transport, tran…...

基于深度学习的线性预测:创新应用与挑战

一、引言 1.1 研究背景 深度学习作为人工智能领域的重要分支&#xff0c;近年来在各个领域都取得了显著的进展。在线性预测领域&#xff0c;深度学习也逐渐兴起并展现出强大的潜力。传统的线性预测方法在处理复杂数据和动态变化的情况时往往存在一定的局限性。而深度学习凭借…...

网络编程3

day3 一、服务器模型 1.循环服务器模型 同一个时刻只能响应一个客户端的请求 2.并发服务器模型 2.1含义 同一个时刻可以响应多个客户端的请求&#xff0c;常用的模型有多进程模型/多线程模型/IO多路复用模型。 2.2多进程模型 每来一个客户端连接&#xff0c;开一个子进程来专门…...

数字化时代下的工业物联网智能体开发平台策略

1. 引言 1.1 工业物联网智能体的发展背景 随着工业4.0的兴起和数字化转型的不断深入&#xff0c;工业物联网(IIoT)已成为推动制造业创新发展的关键技术之一。智能体作为工业物联网的核心组成部分&#xff0c;其开发平台的建设与应用对于实现智能化升级、提升生产效率、降低…...

[Java实战经验]异常处理最佳实践

一些好的异常处理实践。 目录 异常设计自定义异常为异常设计错误代码&#xff08;状态码&#xff09;设计粒度全局异常处理异常日志信息保留 异常处理时机资源管理try-with-resources异常中的事务 异常设计 自定义异常 自定义异常设计&#xff0c;如业务异常定义BusinessExce…...

海拔与大气压关系,大气压单位,气压传感器对比

mbmbar 毫巴(百帕) mbar 毫巴(百帕) hPa 百帕 1百帕1毫巴3/4毫米水银柱 1Kpa10百帕7.5毫米汞柱7.5mmhg 1Bar0.1MPa1000mba1000hpa100*7.5mmhg75mmhg1个大气压 HP303B HP303S HP203N BMP280...

探秘STM32如何成为现代科技的隐形引擎

STM32单片机原理与应用 前言&#xff1a;微型计算机的硅脑 在我们身边的每一个智能设备中&#xff0c;都隐藏着一个小小的"硅脑"——单片机。它们体积微小&#xff0c;却能执行复杂的运算和控制功能&#xff0c;就像是现代科技世界的"神经元"。STM32系列…...

Linux 进程概念补充 (自用)

进程概念 内核进程进程状态内存泄漏进程调度。Linux真实调度算法环境变量 内核 狭义上的操作系统指的是 内核就是进程管理进程调度&#xff0c;文件系统等等。 广义上的操作系统其实在外壳指令这些。封装了系统调用的东西。 进程 课本概念程序的一个基本实例 内核观点&#…...

动态调整映射关系的一致性哈希负载均衡算法详解

一、核心原理与设计要点 双重映射结构 一致性哈希负载均衡通过 哈希环 和 槽动态分配 实现双重映射关系&#xff1a; • 哈希环构建&#xff1a;将节点&#xff08;物理或虚拟&#xff09;和数据键&#xff08;Key&#xff09;通过哈希函数&#xff08;如MD5、CRC32&#xff09…...