【C语言】——内存函数的使用及模拟实现

【C语言】——内存函数的使用及模拟实现

前言

  在【C语言】——字符串函数的使用与模拟实现(上)与【C语言】——字符串函数的使用与模拟实现(下)二文中,我曾详细介绍了字符串相关函数的具体功能及其模拟实现。
  
  然而,实践过程中,我们往往不仅仅只是处理字符串类型的变量,若要对浮点型、整型、结构体等其他类型的数据进行相关操作,又该怎么办呢?
  
  这时,就需要用到的内存操作函数
  
  接下来,让我们一起来学习学习吧

  
  

一、 m e m c p y memcpy memcpy 函数

1.1、函数功能

(1)函数名理解

  

在介绍 m e m c p y memcpy memcpy函数 的功能前,我们先来对函数名进行分析:

  • m e m mem mem 表示的是 m e m o r y memory memory,它有记忆内存的意思,这里它表示的是内存
  • c p y cpy cpy c o p y copy copy,表示拷贝

  这样我们从名字就能大体知道这个函数的大致功能啦,像之前的 s t r c p y strcpy strcpy函数,它是实现对字符串的拷贝,而 m e m c p y memcpy memcpy函数,实现的是对内存块的拷贝
  
  

(2)函数介绍

在这里插入图片描述

功能:将源字符串的 n u m num num内存块内容拷贝到目标字符串中

  • 函数 m e m c p y memcpy memcpy s o u r c e source source 的位置开始向后复制 n u m num num 个字节的数据到 d e s t i n a t i o n destination destination 指向的内存为止
  • 函数的使用需包含头文件 < s t r i n g . h > <string.h> <string.h>
  • 因为拷贝的数据不一定是字符串,函数在遇到 ‘\0’ 时不会停下来
  • 如果 s o u r c e source source d e s t i n a t i o n destination destination 有任何的重叠,复制的结果都是未定义的
  • d e s t i n a t i o n destination destination 所指向的空间必须足够大,至少 n u m num num 个字节大小,以避免溢出
  • 返回值是目标空间的地址

注:对于重叠的内存,交给 m e m m o v e memmove memmove函数 来处理。

  

1.2、函数的使用

  
例如:我们将整型数组 a r r 1 arr1 arr1 的前 5 个元素复制给 a r r 2 arr2 arr2 数组
  
代码如下:

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

  
运行结果:
在这里插入图片描述
  
  可能有些小伙伴在调用 m e m c p y memcpy memcpy 函数时, n u m num num 的传参会写成 5 :memcpy(arr2, arr1, 5);,这里需要注意: m e m c p y memcpy memcpy 拷贝是内存块为单位,即字节,而不是要拷贝的元素的个数。
  当然, n u m num num 的传参也可以写成 元素个数 ∗ * s i z e o f sizeof sizeof(类型)
  如:上述代码调用 m e m c p y memcpy memcpy 函数可写成:memcpy(arr2, arr1, 5*sizeof(int));
  
  那如果用 m e m c p y memcpy memcpy 拷贝重叠空间会怎样呢?我们用自己写的模拟函数试一下

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

  
运行结果:
在这里插入图片描述
  
为什么会这样呢?具体原因稍后解释。
  
  

1.3、函数的模拟实现

  

   m e m c p y memcpy memcpy函数 的实现思路和 s t r c p y strcpy strcpy 的实现差不多,即逐一拷贝每个内存块(详情请看:【C语言】——字符串函数的使用与模拟实现(上))
  

void* my_memcpy(void* dest, void* src, size_t num)
{void* ret = dest;assert(dest && src);while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}return ret;
}

  
代码逐行解析:

  • 参数用 v o i d void void* 的原因: m e m c p y memcpy memcpy 拷贝的是任意类型的数据,而函数事先并不知道你所用传递的数据的类型,因此用 v o i d void void * 指针,以便能接收所有类型的数据
  • assert(dest && src);断言,后面要对指针进行解引用,因此不能传入空指针。
  • while (num--)循环条件的判断, n u m num num 不为 0,即为真,进入循环,后 n u m num num 自减
  • *(char*)dest = *(char*)src;【C语言】—— 指针一 : 初识指针(上)一文中,曾提到不能对 v o i d void void * 指针进行解引用操作,因此要改变其所指向的值,要对其进行强制类型转换。因为 c h a r char char * 类型一次的访问权限只有一个字节,因此将其强转成 c h a r char char* 类型,再进行解引用、赋值
  • dest = (char*)dest + 1;指针向后移,指向下一个字节
      

  这里有小伙伴可能会问:dest = (char*)dest + 1;我觉得有点啰嗦,能不能写成(char*)dest++;呢?
  答案是:不能
  为什么呢?
  因为强制类型转换是临时的。这里的 ++,不是对 c h a r char char* 类型的 d e s t dest dest++,而是对 v o i d void void* 类型 d e s t dest dest++
  上述代码可拆分成两句:(char*)dest;dest++;
  
  那((char*)dest)++;可行吗?
  基本是没问题的,但需要注意的是,有部分编译器可能编不过去。
  
  因此,循环语句的内容也可写成:

	while (num--){*((char*)dest)++ = *((char*)src)++;}

当然,最好还是上面的那种写法。

  
  

二、 m e m m o v e memmove memmove 函数

2.1、函数功能

  
  在前面将 m e m c p y memcpy memcpy函数 时,我曾提到,对于重叠的内存,交给 m e m m o v e memmove memmove函数 来处理,那么 m o m m o v e mommove mommove函数 又是做什么的呢?
  

在这里插入图片描述

功能:将源字符串的 n u m num num内存块内容拷贝到目标字符串中

  • m e m c p y memcpy memcpy函数 的差别就是 m e m m o v e memmove memmove函数 处理的源内存块和目标内存块可以重叠的。
  • 如果源空间和目标空间出现重叠,就得使用 m e m m o v e memmove memmove函数 处理。

  

2.2、函数的使用

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

  
运行结果:
在这里插入图片描述

  
图示:

在这里插入图片描述
  
  可以看到,复制 5 个元素,目标空间与源空间重合了,这时,就不能再使用 m e m c p y memcpy memcpy 函数,而要用 m e m m o v e memmove memmove 函数了
  
在这里插入图片描述

  
  

2.3、函数的模拟实现

(1)思路一

  
  要实现重叠空间的拷贝,有什么办法呢?

  我们想到,可以另外开辟一个数组,将原数组所有数据进行拷贝。然后,再对需要拷贝的元素进行拷贝。

在这里插入图片描述

  但是,这个方法太过繁琐,而且要单独创建数组,浪费空间,这里我们暂不考虑。

  

(2)思路二

  那还有什么办法呢?我们不妨想一想,为什么内存空间重叠不能用memcpy函数?因为新拷贝的内容会将原来真正想拷贝的内容覆盖,这样最终拷贝出来的结果就不是自己想要的
  
图示:

在这里插入图片描述

  那有什么办法可以让先拷贝的内容不覆盖后拷贝的内容呢?

  我们可以从后往前拷贝,如图:
  在这里插入图片描述

  
  这时,有小伙伴就说了:只要以后我们都从后往前拷就行了
  但问题又来了,如果是从 a r r arr arr + 2 开始拷贝 5 个元素到 a r r arr arr 呢?
  
如图:

在这里插入图片描述

  
  发现又出问题了,但问题和之前一样,先拷贝的内容覆盖了原数据,导致后拷贝的内容错误
  

  这时就需要用到从前往后拷贝
  在这里插入图片描述

  
  那什么时候从后往前拷,什么时候从前往后拷呢?
  
结论:

  • 当两块空间重叠时,目标空间在源空间前面从前向后拷贝
  • 当两块空间重叠时,目标空间在源空间后面从后向前拷贝
  • 当两块空间不重叠时,两种拷贝方法都行
      

图示:

在这里插入图片描述

  
  

(3)代码实现

  
  这里,空间不重叠的部分我们用从后向前,因为这样只用在 s r c src src 处分界即可

void* my_memmove(void* dest, void* src, size_t num)
{void* ret = dest;assert(dest && src);if (src > dest){while (num--){//前->后*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{//后->前while (num--){*((char*)dest + num) = *((char*)src + num);}}return 0;
}

  
  这段代码从前往后的情况与 m e m c p y memcpy memcpy 情况相同,就不再过多解释,来看从后往前的情况

  • while (num--)这里循环判断条件与上面一样,当 n u m num num 大于 0,进入循环,随后进行自减。
  • *((char*)dest + num) = *((char*)src + num);这里我们发现,如果要拷贝 20 字节内容,即 n u m num num 等于 20,那么起始位置加 n u m num num - 1 所指向的就是最后一个要拷贝的字节,而 n u m num num 在进入循环后已经自减,因此这里直接加 n u m num num 。再接着,再次循环, n u m num num 再次自减,拷贝倒数第二个字节内容,以此类推,直至 n u m num num 为 0 拷贝结束。
      
      

三、 m e m s e t memset memset 函数

3.1、函数功能

在这里插入图片描述

功能:设置内存,将内存中的值以字节为单位设置成想要的内容

  • p t r ptr ptr:指向要设置的内存块
  • v a l u e value value:要设置的值
  • n u m num num:要设置字节的个数
  • 返回值:要设置的内存,即 p t r ptr ptr
      

3.2、函数的使用

#include<stdio.h>
#include<string.h>int main()
{char str[] = "hello world";memset(str, 'x', 6);printf(str);printf("\n);return 0;
}

  
运行结果:
在这里插入图片描述

  
  这时,有小伙伴问能不能用它来设置整型呢?当然可以

#include<stdio.h>
#include<string.h>int main()
{int arr[10];memset(arr, 0, 10*sizeof(int));int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

  
运行结果:
在这里插入图片描述
  
  那又有小伙伴问全部设置成 1 可以吗?我们来试试

#include<stdio.h>
#include<string.h>int main()
{int arr[10];memset(arr, 1, 10*sizeof(int));int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

  
运行结果:
在这里插入图片描述
  为什么会这样?
  我们要注意: m e m s e t memset memset 是以字节为单位来设置的。
  1 的二进制是 00000000 00000000 00000000 00000001
  而 m e m s e t memset memset 是将每个字节单独设置成 1,即一个整型被设置成:00000001 00000001 00000001 00000001,转换成二进制即:16843009

  

3.3、函数的模拟实现

void* memset(void* ptr, int value, size_t num)
{assert(ptr);while (num--){*((char*)ptr + num) = value;}return ptr;
}

  
  

四、 m e m c m p memcmp memcmp 函数

4.1、函数功能

在这里插入图片描述

功能:比较从 p t r 1 ptr1 ptr1 p t r 2 ptr2 ptr2 指针指向的位置开始,向后的 n u m num num 个字节
  
返回值如下:

在这里插入图片描述

  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2。

  

4.2、函数的使用

int main()
{char buffer1[] = "DWgaOtP12df0";char buffer2[] = "DWGAOTP12DF0";int n = 0;n = memcmp(buffer1, buffer2, 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);elseprintf("'%s' is the same '%s'.\n", buffer1, buffer2);return 0;}

  
运行结果:
在这里插入图片描述
  

4.3、函数的模拟实现

int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{assert(ptr1 && ptr2);while (*(char*)ptr1 == *(char*)ptr2 && num--){ptr1 = (char*)ptr1 + 1;ptr2 = (char*)ptr2 + 1;}return *(char*)ptr1 - *(char*)ptr2;
}

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

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

相关文章

HarmonyOS开发环境搭建 移动开发 鸿蒙开发 ArkTS

&#x1f4dc;目录 &#x1f4a1; 环境搭建 &#x1f680;安装nodejs &#x1f935;安装ohpm &#x1f354;安装SDK &#x1f4a5;Emulator安装 &#x1f336;️新建ArkTs项目 &#x1f3c6;️ArkTS语言 ✨️基本语法 &#x1f388; 声明式UI描述 &#x1f371;组件 …

java的单元测试和反射

单元测试 就是针对最小的功能单元&#xff0c;编写测试代码对其进行正确性测试 Junit单元测试框架&#xff1a; 可以用来对方法进行测试 有点&#xff1a; 可以灵活的编写测试代码&#xff0c;可以针对某个方法进行测试&#xff0c;也支持一键完成对全部方法的自动发测试&a…

理解JMM

JMM 对volatile的理解 volatile 是java虚拟机提供轻量级的同步机制 1、保证可见性 2、不保证原子性 3、禁止指令重排 那么可见性与JMM相关 什么是JMM Java内存模型&#xff0c;不存在的东西&#xff0c;是一个概念&#xff0c;是一个约定 线程加锁前&#xff0c;必须读取…

uni-app 如何添加模拟器

uni-app 如何添加模拟器 使用微信开发者工具运行微信小程序使用 HBuilderX 内置模拟器使用第三方 Android 模拟器 下载并安装&#xff1a;配置环境&#xff1a;连接模拟器&#xff1a; 总结 有哪些可以使用的安卓模拟器软件 uni-app 如何添加模拟器 Uni-App 是一个基于 Vue.js…

认知觉醒 PDF电子版 下载

认知觉醒 PDF电子版 开启自我改变的原动力 周岭 / 人民邮电出版社 / 2020-10 链接&#xff1a;https://pan.baidu.com/s/1EHUK_AhvE5TWAZsYXFQ5QA?pwdwrho 提取码&#xff1a;wrho

基于IIoT的设备预测性维护设计

基于IIoT的设备预测性维护设计 一、引言 在工业物联网&#xff08;IIoT&#xff09;的背景下&#xff0c;设备预测性维护成为了一种关键的战略&#xff0c;能够帮助企业提前发现并解决设备故障&#xff0c;从而提高生产效率、减少停机时间&#xff0c;并降低总体维护成本。为了…

uniapp 如何区分目前运行环境(app、web、mp-weixin)

platform 区分 iOS、Android uniplatform 区分 app、web、mp-weixin ....

GPT-3.5 Turbo 的 temperature 设置为 0 就是贪婪解码?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 将 GPT-3.5 Turbo 的 temperature 设置为 0 通常意味着采用贪婪解码&#xff08;greedy decoding&#xff09;策略。在贪婪解码中&#xff0c;模型在每一步生成文本时选择概率最高的词元&#xff0c;从…

Microchip 32位MCU CAN驱动图文教程-附源码

文章目录 创建一个新的32位MCU工程Microchip MCC Harmony配置界面说明在MCC下配置系统的时钟在MCC下配置所需要使用的模块配置调试打印模块配置CAN模块配置管脚功能修改系统堆栈大小生成代码 添加用户代码 创建一个新的32位MCU工程 确保电脑上已经安装最新的MPlab X IDE、XC32编…

ubuntu安装QEMU

qemu虚拟机的使用&#xff08;一&#xff09;——ubuntu20.4安装QEMU_ubuntu安装qemu-CSDN博客 遇到的问题&#xff1a; (1)本来使用git clone https://github.com/qemu/qemu.git fatal: 无法访问 https://github.com/qemu/qemu.git/&#xff1a;GnuTLS recv error (-110): …

[阅读笔记18][CITING]LARGE LANGUAGE MODELS CREATE CURRICULUM FOR INSTRUCTION TUNING

这篇论文是23年10月提交到arxiv上的&#xff0c;也是用大模型蒸馏小模型的思路。 作者在这篇论文中提出了课程指令微调&#xff0c;大体流程如下图所示&#xff0c;教师模型给出一个问题&#xff0c;让学生模型回答一下&#xff0c;这时候学生回答大概率不够准确&#xff0c;这…

深度学习之图像分割从入门到精通——基于unet++实现细胞分割

模型 import torch from torch import nn__all__ [UNet, NestedUNet]class VGGBlock(nn.Module):def __init__(self, in_channels, middle_channels, out_channels):super().__init__()self.relu nn.ReLU(inplaceTrue)self.conv1 nn.Conv2d(in_channels, middle_channels, …

生态短讯 | Tapdata 与 TDengine 完成产品兼容性互认证,打造物联网实时数据生态

近月&#xff0c;深圳钛铂数据有限公司&#xff08;以下简称钛铂数据&#xff09;自主研发的实时数据平台&#xff08;Tapdata Live Data Platform&#xff09;与北京涛思数据科技有限公司&#xff08;以下简称涛思数据&#xff09;自主研发的大数据平台 TDengine&#xff0c;已…

【深度学习】Dropout、DropPath

一、Dropout 1. 概念 Dropout 在训练阶段会让当前层每个神经元以drop_prob&#xff08; 0 ≤ drop_prob ≤ 1 0\leq\text{drop\_prob}\leq1 0≤drop_prob≤1&#xff09;的概率失活并停止工作&#xff0c;效果如下图。 在测试阶段不会进行Dropout。由于不同批次、不同样本的神…

数据库管理-第171期 Oracle是用这种方式确保读一致的(20240418)

数据库管理171期 2024-04-18 数据库管理-第171期 Oracle是用这种方式确保读一致的&#xff08;20240418&#xff09;1 基本概念2 用处3 注意事项总结 数据库管理-第171期 Oracle是用这种方式确保读一致的&#xff08;20240418&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#x…

MySQL中explain的用法

执行结果各字段的含义 EXPLAIN SQL语句 如&#xff1a; EXPLAIN SELECT * FROM test 执行结果&#xff1a; 列名描述id在一个大的查询语句中每个SELECT关键字都对应一个 唯一的idselect_typeSELECT关键字对应的那个查询的类型table表名partitions匹配的分区信息type针对单表…

P2P面试题

1&#xff09;描述一下你的项目流程以及你在项目中的职责&#xff1f; 一个借款产品的发布&#xff0c;投资人购买&#xff0c;借款人还款的一个业务流程&#xff0c;我主要负责测注册&#xff0c;登录&#xff0c;投资理财这三个模块 2&#xff09;你是怎么测试投资模块的&am…

HttpServlet,ServletContext,Listener它仨的故事

1.HttpServlet。 听起来是不是感觉像是个上古词汇&#xff0c;是不是没有阅读下去的兴趣了&#xff1f;Tomcat知道吧&#xff0c;它就是一个servlet容器&#xff0c;当用户向服务器发送一个HTTP请求时&#xff0c;Servlet容器&#xff08;如Tomcat&#xff09;会根据其配置找到…

overflow(溢出)4个属性值,水平/垂直溢出,文字超出显示省略号的详解

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合…

解析 IP(IPv4)地址

IPv 4 地址 一、组成二、IPv4 的分类三、子网掩码四、特殊的地址五、私有 IP 地址六、全局 IP 地址七、私有 IP 地址和全局 IP 地址的关系八、广播地址九、网络地址十、IP 地址个数计算十一、查看电脑的 IP 地址&#xff08;window&#xff09;十二、手动设置电脑的 IP 地址 为…