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

Tiny Spark get dazzling some day.

目录

  • 1. memcpy
    • -- 函数原型
    • -- 函数使用
    • -- 函数的模拟实现
  • 2.memmove
    • -- 函数原型
    • -- 函数使用
    • -- 函数的模拟实现
  • 3. memset
    • -- 函数原型
    • -- 函数使用
    • -- 函数的模拟实现
  • 4. memcmp
    • -- 函数原型
    • -- 函数使用
    • -- 函数的模拟实现

1. memcpy

  • 使用需包含头文件:<string.h>

– 函数原型

#include <string.h> // 头文件
void* memcpy ( void* destination, const void* source, size_t num ); 
			       目标空间               源内容         拷贝数目

memcpy 函数的作用,是将一块空间(源)的前 num 个字节 的内容(注意!是字节不是字符个数),赋值粘贴到另一个空间(目标)。

规则:

  1. 源指针目标指针 指向的对象的 基础类型 与此函数无关,即: memcpy函数对任意类型的数据进行操作,例如字符串,数组和结构体等。
  2. memcpy 函数 不会检测 源内容中是否存在结束标志 ’ \0 ',要求拷贝多少字节的内容就精确地拷贝多少字节的内容。
  3. 为避免溢出,两个参数 所指向的空间最好不是同一个(否则可能会造成重叠问题,这是更推荐使用的是 memmove 函数)。
  4. 确保 目标空间 有足够位置存放要复制的内容。

这哥们有点和 字符串函数的 strcpy 函数类似,都是把一段内容复制粘贴到另一个空间。不同的是 strcpy 只能复制 字符串内容, 而 memcpy 能复制任意类型的内容。


– 函数使用

int dest_arr[10]; 
int src_arr[] = { 1, 2, 3, 4, '\0', 5, 6, 7, 8, 9 };
int sz = sizeof(src_arr) / sizeof(src_arr[0]);
memcpy(dest_arr, src_arr[], sz * sizeof(int));// 把整个源数组的内容都复制过去
此时 dest_arr[10] 里面的内容就是 1, 2, 3, 4, 0, 5, 6, 7, 8, 9

memcpy 并没有到 ’ \0 '停止了而是继续复制,我理解为函数并没有去检测 ’ \0 ’ 的含义,而是仅仅将其当作数组元素一个数据而已。

我们再来看看第二种—复制字符串:

char dset_str[20];
char src_str[] = "Jackie\0Chan";
int sz = sizeof(src_str) / sizeof(src_str[0]);
memcpy(dest_str, src_str, sz * sizeof(char);// 把整个源数组的内容都复制过去
//此时 dest_arr[10] 里面的内容就是 Jackie\0Chan
printf("%s", dest_str);
// 但是,在 printf 打印的时候不要误跟着以为 也会不检测'\0'而打印 "Jackie\0Chan"
// 从而可能一脸懵逼 (me),或者 "这不常识?~不屑" (大佬you)

第三种–如果 源空间目标空间 重叠时使用情况:

int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 ,10 };
memcpy(arr + 2, arr, 8 * sizeof(int));// 源空间开头为 第三个元素的位置 
for(int i= 0; i < 10; i++)
	printf("%d ",  arr[i]);

在VS2022上可能直接用可能会正常打印 1 2 1 2 3 4 5 6 7 8
但如果用 模拟函数实现的方法 执行程序:
在这里插入图片描述

这种是错误的
下面来解释下:
在这里插入图片描述
所以,若接着往下走,最后的输出结果就是

1 2 1 2 1 2 1 2 1 2

所以在使用 memcpy 函数时尽量不要 将 目标指针源指针 指向同一块空间。


– 函数的模拟实现

#include <assert.h>
void* Sim_memcpy(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dest);
	assert(src);
	//先检测 dest 和 src 是否为空指针
	while (num--) 
	{
		*(char*)dest = *(char*)src;// 非常精细,一个字节一个字节地复制
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
 return(ret);
}

2.memmove

  • 使用需包含头文件:<string.h>

– 函数原型

void* memmove ( void* destination, const void* source, size_t num );
                    目标空间				  源内容         拷贝数目

memmove 函数的作用 和 memcpy 基本相同,是将一块空间(源)的前 num 个字节 的内容(注意!是字节不是字符个数),赋值粘贴到另一个空间(目标)。
但是, memcpy 不能作用在 同一块空间, 也就是无法在空间重叠下复制,而 memmove 可以解决这个问题。

规则:

  1. 源指针目标指针 指向的对象的 基础类型 与此函数无关,即: memmove函数对任意类型的数据进行操作,例如字符串,数组和结构体等。
  2. memmove 函数 不会检测 源内容中是否存在结束标志 ’ \0 ',要求拷贝多少字节的内容就精确地拷贝多少字节的内容。
  3. memmove在拷贝时会有一个 缓冲区 来接受源空间,所以允许 源空间目标空间 重叠。
    4。 确保 目标空间 有足够的位置来存放复制的内容。

– 函数使用

第一种使用情况和 memmove 基本相同

int dest_arr[10]; 
int src_arr[] = { 1, 2, 3, 4, '\0', 5, 6, 7, 8, 9 };
int sz = sizeof(src_arr) / sizeof(src_arr[0]);
memmove(dest_arr, src_arr[], sz * sizeof(int));// 把整个源数组的内容都复制过去
此时 dest_arr[10] 里面的内容就是 1, 2, 3, 4, 0, 5, 6, 7, 8, 9

第二种—如果 源空间 和 目标空间 重叠时使用情况:

int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 ,10 };
memmove(arr + 2, arr, 8 * sizeof(arr));
for(int i= 0; i < 10; i++)
	printf("%d ",  arr[i]);

结果是:

1 2 1 2 3 4 5 6 7 8

那为什么 memcpy 无法满足实现空间重叠的情况,而 memmove 却可以呢?
在这里插入图片描述
所以,在往后遇到 目标指针源指针 指向的空间发生空间重叠时,可以使用 memmove 函数来解决。

– 函数的模拟实现

#include <assert.h>
void* Sim_memmove(void* dest, const void* src, size_t num)
{
 	void* ret = dest;assert(dest);
	assert(src);
	//先检测 dest 和 src 是否为空指针
  	if (dest <= src || (char*)dest >= ((char*)src + num)) // 从前往后拷
  	{
    	while (num--) 
    	{
        	*(char*)dest = *(char*)src;
        	dest = (char*)dest + 1;
        	src = (char*)src + 1;
 		}
 	}
  	else // 从后往前拷
  	{
    	dest = (char*)dest + count - 1;
    	src = (char*)src + count - 1;
    	while (numt--) 
    	{
        	*(char*)dest = *(char*)src;
        	dest = (char*)dest - 1;
        	src = (char*)src - 1;
    	}
    }
  	return(ret);
}

有点小难理解,下面我们来看看:

首先定义一个数组:int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

第一种情况:

Sim_memmove(arr + 2, arr, 5 * sizeof(int));

`在这里插入图片描述
第二种情况:

Sim_memmove(arr, arr + 2, 5 * sizeof(int));

在这里插入图片描述
所以在模拟实现 memmove函数时,要确定拷贝顺序是 从前往后 还是 从后往前
在这里插入图片描述


3. memset

  • 使用需包含头文件:<string.h>

– 函数原型

void* memset ( void* ptr, int value, size_t num );

memset 函数用来将 ptr 指向的内存块的的指定范围(num个 字节)设置为指定值。

– 函数使用

char str[] = "Hello World";
memset(str, 'X', 5);
printf("%s", str);

输出结果:

XXXXX World

– 函数的模拟实现

#include <assert.h>
void* Sim_memset(void* ptr, int value, size_t num)
{
	void* ret = ptr;
	assert(ptr != NULL);
	// 先检测 ptr 是否为空指针
	while (num--)
	{
		*(char*)ptr = value;
		(char*)ptr += 1;
	}
	return ret;
}

4. memcmp

  • 使用需包含头文件:<string.h>

– 函数原型

int memcmp ( const void* ptr1, const void* ptr2, size_t num );

memcmp 函数用来比较 ptr1 指向的内存块前 num个字节 的内容和 **ptr2 ** 指向的内存块的前 num个字节 的内容。

规则:

  1. 该函数和 strcmp 相似,都是比较函数,但是 memcmp 函数在找到 ’ \0 ’ 字符后不会停止比较。
  2. 该函数会返回一个整形值,该值指示的内存块的内容之间关系如下:
返回值表明
<0在两个内存块中不匹配的第一个字节在 ptr1 中的值低于 ptr2 中的值
=0两个内存块的内容相等
>0在两个内存块中不匹配的第一个字节在 ptr1 中的值大于 ptr2 中的值

– 函数使用

比较两个数组

int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,11 };
int ret = memcmp(arr1, arr2, 10 * sizeof(int));
if (ret < 0)
    printf("arr1 < arr2");
else if (ret == 0)
    printf("arr1 == arr2");
else
    printf("arr1 > arr2");

输出结果:

arr1 < arr2

比较两个字符串

char* str1 = "ABCDE";
char* str2 = "ABCDW";
int ret = memcmp(str1, str2, 10 * sizeof(char));
if (ret < 0)
    printf("arr1 < arr2");
else if (ret == 0)
    printf("arr1 == arr2");
else
    printf("arr1 > arr2");

输出结果

arr1 < arr2

– 函数的模拟实现

int Sim_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
    assert(ptr1 != NULL);
    assert(ptr2 != NULL);
    // 先判断 ptr1 和 ptr2 是否为空指针
    while ((*(char*)ptr1 == *(char*)ptr2) && num)
    {
        ((char*)ptr1)++;
        ((char*)ptr2)++;
    }
    return *(char*)ptr1 - *(char*)ptr2;// 返回该字节内容的对应的 ASCII码表值的 差值
}

该函数的模拟实现和 strcmp 的函数模拟实现有点相似,不同的是,strcmp 需要实现遇到
’ \0 ’ 时便停止比较,而 memcmp 则是直接忽略 ’ \0 ’ 继续比较(字符若为 ’ \0 ’ 也会执行一次比较) 。

  
  
  Stay hungry. Stay Foolish. 饥渴求知,虚怀若愚。
  感谢各位读者支持,虚心请教,如有错漏或可改进点,请任意指出,感激不尽!
  一起进步!


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

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

相关文章

【go项目01_学习记录07】

学习记录 1 创建博文1.1 在main.go中添加路由1.2 构建表单 2 读取表单数据2.1 完善articlesStoreHandler() 函数2.2 修改代码&#xff0c;查看区别 3 表单验证3.1 数据验证3.2 出错提示 1 创建博文 1.1 在main.go中添加路由 访问http://localhost:3000/articles/create 1.2 …

分享几个好用的正规源码交易平台,让开发之路更easy!

在软件开发的世界里&#xff0c;寻找高质量的源码资源对于每一个开发者来说都是至关重要的。它不仅能帮助我们节省大量的开发时间&#xff0c;还能让我们站在巨人的肩膀上&#xff0c;更快地实现项目目标。今天&#xff0c;我就为大家分享几个我亲自使用并觉得非常不错的正规源…

Docker下Open WebUI,Ollama的安装实践

提示一下Open WebUI与ollama的关系。后端的同学可以理解为Open WebUI等于是个Navicat&#xff0c;Ollama就是具体的数据库实例。 官方安装文档&#xff1a; &#x1f3e1; Home | Open WebUI Open WebUI官网文档翻译&#xff1a; 注意&#xff1a; 使用Docker安装Open WebU…

Gradle基础学习(七) 认识插件

Gradle构建在一个插件系统上&#xff0c;本身主要由基础设施组成&#xff0c;比如有一个先进的依赖解析引擎&#xff0c;而其他功能则来自插件。 插件是提供额外功能给Gradle构建系统的软件组件。 插件可以被应用到Gradle构建脚本中&#xff0c;以添加新的任务、配置或其他与构…

C++STL细节,底层实现,面试题04

文章目录 19. STL19.1. 序列容器19.1.1. vector19.1.1.1. 底层实现和特点19.1.1.2. 常用函数19.1.1.3. emplace_back() vs push_back() 19.1.2. array19.1.2.1. 底层实现和特点19.1.2.2. 常用函数 19.1.3. deque19.1.3.1. 底层实现和特点19.1.3.2. 常用函数 19.1.4 list19.1.4.…

【漏洞复现】某小日子太阳能系统DataCube3审计

漏洞描述 某小日子太阳能系统DataCube3终端测量系统 多个漏洞利用方式 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危害国家安全、荣誉和利益,未经授权请勿利用文章中的技术资料对任何计算机系统进…

为什么说TailwindCSS是2024 年前端最优的 CSS 框架?

如果有一本圣经&#xff0c;大家都按照圣经的标准写网页&#xff0c;那世界将更加的标准化和美好。这本圣经就是TailwindCSS。 什么是 Tailwind CSS&#xff1f; Tailwind CSS 是一个流行的 CSS 框架&#xff0c;旨在帮助开发者快速构建现代化的、响应式的 Web 界面。与其他 …

电商选品4大关键指标都不懂?那你别做运营了

电商不管做什么平台&#xff0c;选品是第一步。今天店雷达给大家分享围绕电商选品4大关键数据指标&#xff0c;选好了品&#xff0c;加上合理的有效运营&#xff0c;商品流量指日可爆。 指标一&#xff1a;竞争度 竞争度是选品时需要考量的首要因素。现在市场很卷&#xff0c…

【C++】07.string详解

目录 一、为什么会有string&#xff1f; 二、string的常见接口说明 2.1 string的默认成员函数 2.1.1 默认构造函数 2.1.2析构函数 2.1.3赋值运算符 2.2迭代器介绍 2.2.1 正向迭代器 2.2.2 反向迭代器 2.3 string类对象的容量操作 2.4 string类对象的访问及遍…

【漏洞复现】Apahce HTTPd 2.4.49(CVE-2021-41773)路径穿越漏洞

简介&#xff1a; Apache HTTP Server是一个开源、跨平台的Web服务器&#xff0c;它在全球范围内被广泛使用。2021年10月5日&#xff0c;Apache发布更新公告&#xff0c;修复了Apache HTTP Server2.4.49中的一个路径遍历和文件泄露漏洞&#xff08;CVE-2021-41773&#xff09;。…

轻量级分布式任务调度平台:XXL-JOB

目录 1 介绍1.1 特性1.2 整体架构 2 快速导入2.1 测试工程导入2.1 初始化数据库2.3 Docker安装任务管理中心 3 XXL-JOB任务注册测试3.1 引入xxl-job依赖3.2 配置xxljob相关信息3.3 定义定时任务执行方法3.3 配置任务执行器 4 CRON表达式4.1 cron表达式语法介绍4.2 cron练习 1 介…

Python深度学习基于Tensorflow(7)视觉处理基础

文章目录 视觉基础图像基础卷积层&#xff1a;图像的中全连接层的优化卷积核tf.keras中的卷积函数池化层 现代经典网络DenseNet 数据增强 图像的本质是一个矩阵&#xff0c; 矩阵中的一个点就是一个像素&#xff0c;如果像素大小为 1000 1000 1000 \times 1000 10001000&…

ue引擎游戏开发笔记(36)——为射击落点添加特效

1.需求分析&#xff1a; 在debug测试中能看到子弹落点后&#xff0c;需要给子弹添加击中特效&#xff0c;更真实也更具反馈感。 2.操作实现&#xff1a; 1.思路&#xff1a;很简单&#xff0c;类似开枪特效一样&#xff0c;只要在头文件声明特效变量&#xff0c;在fire函数中…

WSL介绍(Windows10内置的Linux子系统)

最近发现在Windows10下不用安装虚拟机也可以使用Linux&#xff0c;然后发现原来2016年就已经有这个功能了&#xff0c;下面来介绍下如何使用。 首先我的win10版本信息如下&#xff0c;以免部分版本不支持&#xff0c;可以做个参考。 需要进到控制面板里将Linux子系统功能打开&a…

这 7 道 Redis 基础问题,很常见!!

后端项目如果用到分布式缓存的话&#xff0c;一般用的都是 Redis。不过&#xff0c;Redis 不仅仅能做缓存&#xff0c;还能用作分布式锁、延时队列、限流等等。 什么是 Redis&#xff1f; Redis[1] &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的…

leetcode63.跳跃游戏2(动态规划)

问题描述&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。 现在考虑网格中有障碍物…

springboot3+springsecurity+redis 整合登录认证以及权限校验

1. 架构说明 整体架构如下(提供的对应的模块引入)&#xff0c;围绕着springsecurity中的三大核心展开&#xff1a; ​ 1、Authentication&#xff1a;存储了认证信息&#xff0c;代表当前登录用户 ​ 2、SeucirtyContext&#xff1a;上下文对象&#xff0c;用来获取Authenti…

基于一种改进小波阈值的微震信号降噪方法(MATLAB)

微震是指岩体由于在人为扰动或自然原因下受力变形&#xff0c;发生破裂过程中能量积聚而释放的弹性波或应力波。微震信号具有信噪比低、不稳定性、瞬时性和多样性等特点。因此&#xff0c;在任何损坏之前都会出现微小的裂缝&#xff0c;这种微小的裂缝是由岩层中应力和应变的变…

vue使用screenfull实现全屏模式

vue实现全屏模式可以通过第三方依赖screenfull完成效果。 实现效果&#xff1a;查看源码 首先需要安装第三方依赖 // npm npm install screenfull//yarn yarn add screenfull// pnpm pnpm install screenfull代码实现&#xff1a; <div class"flex-center w100 h…

TC8002D(3W音频功放IC)是一颗带关断模式的音频功放IC

一、概述 TC8002D是一颗带关断模式的音频功放IC。在5V输入电压下工作时&#xff0c;负载(3Ω)上的平均功率为3W&#xff0c;且失真度不超过10%。而对于手提设备而言&#xff0c;当VDD作用于关断端时&#xff0c;TC8002D将会进入关断模式&#xff0c;此时的功耗极低&…