【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);

	else
		printf("'%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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vulnhub靶机 DC-6 打靶实战 详细渗透测试过程

Vulnhub靶机 DC-6 详细渗透流程 打靶实战 目录 Vulnhub靶机 DC-6 详细渗透流程 打靶实战一、将靶机导入到虚拟机当中二、渗透测试主机发现端口扫描信息探测web渗透目录爆破爆破后台密码反弹shell搜集有价值信息SSH远程登录提权反弹jens用户权限的shell 提权利用 一、将靶机导入…

Mac 下安装PostgreSQL经验

使用homebrew终端软件管理器去安装PostgreSQL 如果没有安装brew命令执行以下命令 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 沙果开源物联网系统 SagooIoT | SagooIoT 1.使用命令安装postgreSQL brew i…

制作一个RISC-V的操作系统十二-定时器中断

文章目录 CLINT定时器中断mtimemtimecmp机制总体框架流程时间节拍系统时钟代码 CLINT 产生软件中断和定时器中断 定时器中断 mtime 类似计数器&#xff0c;按照硬件对应的固定频率递增 上电后会自动复位为0&#xff0c;有硬件自动完成 mtimecmp 需要自己设置&#xff0…

【SpringBoot实战篇】获取用户详细信息

1 明确需求 1需要获取用户详细信息 2 接口文档 1基本信息 2请求参数 无 3 响应数据 响应数据类型&#xff1a;application/json 响应参数说明&#xff1a; 响应数据样例 3 思路分析 1用户名在请求头里获取 4 开发 4.1 控制器usercontroller GetMapping("/userInfo")p…

Vue3——组件基础

组件基础 1. 组件定义与使用 1.1 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>组件基础&l…

iZotope RX 10 音频修复和增强工具 mac/win

iZotope RX 10 for Mac是一款出色的音频修复和增强工具&#xff0c;凭借其卓越的音频处理技术&#xff0c;能够轻松应对各种音频问题。 无论是背景噪音、回声还是失真&#xff0c;RX 10都能精准去除&#xff0c;还原清晰纯净的音频。同时&#xff0c;它还提供了丰富的增强工具&…

小红书电商运营实战课,从0打造全程实操(65节视频课)

课程内容&#xff1a; 1.小红书的电商介绍 .mp4 2.小红书的开店流程,mp4 3.小红书店铺基础设置介绍 ,mp4 4.小红书店铺产品上架流程 .mp4 5.客服的聊天过程和子账号建立 .mp4 6.店铺营销工具使用和后台活动参加 .mp4 7.小红书产品上架以及拍单教程,mp4 8.小红书如何选品…

第15届蓝桥杯题解

A题 结果&#xff1a;2429042904288 思路很简单 前20个数分别是 20 24 40 48 60 72 80 96 100 120 140 144 160 168 180 192 200 216 220 240 第2 4 6 8 12 ...n个数分别是24的 1倍 2倍 3倍 4倍 6倍 n/2倍 所以第202420242024 个数就是 24的 101210121012倍 B题 答案&am…

在PostgreSQL中如何实现递归查询,例如使用WITH RECURSIVE构建层次结构数据?

文章目录 解决方案使用WITH RECURSIVE进行递归查询示例代码 总结 在PostgreSQL中&#xff0c;递归查询是一种非常强大的工具&#xff0c;它可以用来查询具有层次结构或树形结构的数据。例如&#xff0c;你可能会在员工-经理关系、目录结构或组织结构图中遇到这样的数据。为了处…

嵌入式物联网实战开发笔记-乐鑫ESP32开发环境ESP-IDF搭建【doc.yotill.com】

乐鑫ESP32入门到精通项目开发参考百例下载&#xff1a; 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;4e33 3.1 ESP-IDF 简介 ESP-IDF&#xff08;Espressif IoT Development Framework&#xff09;是乐鑫&#xff08;Espressif Systems&#xff09;为 ESP 系列…

跨站攻击CSRF实验

1.low等级 先利用Burp抓包 将get响应的url地址复制&#xff0c;发到网页上&#xff08;Low等级到这完成&#xff09; Medium&#xff1a; 再将抓到的包发到Repeater上,对请求中的Referer进行修改&#xff0c;修改成和url一样的地址&#xff0c;修改成功。 在这里修改后发送 然…

Java中的四种引用类型

6.Java中的引用类型 1.强引用 一个对象A被局部变量、静态变量引用了就产生了强引用。因为局部变量、静态变量都是被GC Root对象关联上的&#xff0c;所以被引用的对象A&#xff0c;就在GC Root的引用链上了。只要这一层关系存在&#xff0c;对象A就不会被垃圾回收器回收。所以只…

Fannel和Calico

一 1、路由器下面每一个端口都是一个vlan,隔离了广播包 192.168.1.0和192.168.2.0他们属于不同的vlan,没有三层交换机或者路由器,他们通不了信 不在同一个vlan,也就是子网,包就会走向网关(也就是路由器那里,路由器有路由表。查看目的地192.168.2.0在b口,从b口出去vlan…

Axure RP 9中文激活版:专业原型设计工具mac/win

Axure RP 9是一款由美国Axure Software Solution公司开发的专业原型设计工具。它凭借强大的交互功能和丰富的设计素材&#xff0c;为产品经理、UI设计师、交互设计师等用户提供了高效、便捷的原型设计体验。 Axure RP 9支持快速创建线框图、流程图、原型和规格说明文档&#xf…

Python可视化数据分析-饼状图

一、前言 饼状图&#xff08;Pie Chart&#xff09;是一种常用的数据可视化图表&#xff0c;用于展示数据中各部分的占比关系。Python 中有多种库可以用于绘制饼状图&#xff0c;比较常用的包括 matplotlib、pyecharts和 plotly 等。 二、使用 matplotlib 绘制饼状图 import…

【Java并发知识总结 | 第八篇】Java并发线程知识总结

文章目录 8.Java线程知识总结8.1线程定义8.2Java中创建线程的三种方式8.2.1继承Thread类8.2.2实现Runnable接口8.2.3实现Callable接口&#xff08;与线程池搭配使用&#xff09;8.2.4小结 8.3线程的六种状态8.4sleep()和wait()的区别8.5线程上下文切换8.6线程之间的通信方式8.6…

【正则表达式】正则表达式基本语法元素

目录 字符类量词边界匹配逻辑和分组转义和特殊字符验证正则表达式是否能够成功提取数据 字符类 .&#xff1a;匹配除换行符之外的任何单个字符。 [abc]&#xff1a;匹配方括号内的任何字符。 [^abc]&#xff1a;匹配不在方括号内的任何字符。 [a-z]&#xff1a;匹配任何小写字…

51单片机入门_江协科技_31~32_OB记录的自学笔记_LCD1602液晶显示屏

31. LCD1602 31.1. LCD1602介绍 •LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶显示屏是一种字符型液晶显示模块&#xff0c;可以显示ASCII码的标准字符和其它的一些内置特殊字符&#xff0c;还可以有8个自定义字符 •显示容量&#xff1a;162个字符&#xff0c…

使用Redis实现全局唯一ID

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 在分布式系统中&am…

插入排序动态展示3(Python可视化源代码)

修改了“开始”命令按钮&#xff0c;每次单击“开始”&#xff0c;都重新排序。 Python代码 import tkinter as tk import random import timeclass InsertionSortVisualizer:def __init__(self, root, canvas_width800, canvas_height400, num_bars10):self.root rootself.…