【C语言】内存函数详解与模拟实现

文章目录

  • 拓展:
  • Ⅰ. memcpy -- 内存拷贝
    • 1、函数介绍与使用
    • 2、模拟实现
  • Ⅱ. memmove -- 内存拷贝
    • 1、函数介绍与使用(与memcpy函数的区别)
    • 2、模拟实现
  • Ⅲ. memcmp -- 内存比较
    • 1、函数介绍与使用
    • 2、模拟实现
  • Ⅳ. memset -- 内存设置
    • 1、函数介绍与使用
    • 2、模拟实现

在这里插入图片描述

拓展:

符号扩展是指将有符号类型的值转换为更宽的类型时,如果符号位为 1,则在更高位填充 1,以保持符号不变。

​ 例如,将 signed char 类型的值 -1 转换为 int 类型时,符号位为 1,因此在 int 类型的高位填充 1,得到的值为 -1

​ 在实现内存操作函数时,符号扩展可能会导致错误的结果。例如,当我们使用 char 类型表示内存中的每个字节时,如果对于一个带符号的 char 变量,其值为 -128,那么在进行比较或赋值时,将会发生符号扩展,导致实际比较或赋值的值不是我们期望的值。

为了避免符号扩展的问题,我们应该使用无符号类型来表示内存中的每个字节。由于无符号类型不会发生符号扩展,它可以更好地保持实际值不变。在实际使用中,应该根据具体情况选择合适的类型,以确保函数的正确性和安全性。

Ⅰ. memcpy – 内存拷贝

1、函数介绍与使用

#include <string.h>
void *memcpy(void *dest, const void *src, size_t count);

memcpy 函数是一个用于拷贝两个不相关的内存块的函数。memcpy 函数会从 src 的位置开始向后复制 count 个字节的数据到 dest 的内存位置,并返回 dest 的首地址。

☢️注意:

  • memcpy 函数在遇到 \0 的时候并不会停下来。
  • destsrc 有任意重叠,复制的结果都是未定义的(未拷贝内容被覆盖)。
  • memcpy 函数在实现的时候,不知道未来会被用来拷贝什么样的数据,所以参数的指针类型为 void*(可接收任意类型指针)。

举个例子,比如我们要将 arr1 数组中的 1,2,3,4 拷贝到 arr2 数组中。

#include <stdio.h>
#include <string.h>

int main()
{
	int arr1[4] = { 1, 2, 3, 4 };
	int arr2[6];
	memcpy(arr2, arr1, sizeof(int)*4);

	for (int i = 0; i < 4; ++i)
		printf("%d ", arr1[i]);
	printf("\n");
	
	for (int i = 0; i < 6; ++i)
		printf("%d ", arr2[i]);
	return 0;
}

// 运行结果:
1 2 3 4
1 2 3 4 -858993460 -858993460

​ 因为一个整型的大小是 4 个字节,而我们要拷贝 4 个整型到 arr2 中,所以传参的时候第三个参数 count 的大小为 16

2、模拟实现

​ 进入函数体首先保存 dest 的起始位置,便于之后返回。然后循环 count 次,每次将 src 中的一个字节的内存数据拷贝到 dest 中的对应位置,然后 destsrc 指针后移继续拷贝,拷贝结束后返回 dest 原来的首地址即可。

void* my_memcpy(void* dest, const void* src, size_t count)
{
    if (dest == NULL || src == NULL)  // 检查指针是否为空
        return NULL;

    char* pDest = (char*)dest;     
    const char* pSrc = (const char*)src;

    if (pDest > pSrc && pDest < pSrc + count)  // 检查是否重叠,也就是dest在src要复制空间的范围内
    {
        // 重叠要反向拷贝
        for (size_t i = count; i > 0; --i)
            pDest[i - 1] = pSrc[i - 1];
    }
    else
    {
        for (size_t i = 0; i < count; ++i)
            pDest[i] = pSrc[i];
    }
    return dest;
}

​ 这个 if 语句的作用是检查内存区域是否重叠。如果内存区域重叠,则需要反向拷贝,以免出现数据覆盖的情况。

​ 具体来说,如果目标地址 dest 比源地址 src 更靠后(即 pDest > pSrc),并且目标地址 dest 在源地址 src 和源地址 src+count 之间(即 pDest < pSrc + count),则说明 内存区域重叠。此时我们需要反向拷贝,以免在拷贝过程中覆盖未拷贝的数据。

​ 举个例子,假设有一个长度为 5 的内存区域,其起始地址为 0x100。现在要将这个内存区域拷贝到起始地址为 0x104 的位置。在这种情况下,如果我们按照正常的方式从前往后拷贝,那么在拷贝到第 3 个字节时,会将原来的第 3 个字节覆盖掉。因此,我们需要从后往前拷贝,以免出现这种情况。

Ⅱ. memmove – 内存拷贝

1、函数介绍与使用(与memcpy函数的区别)

#include <string.h>
void *memmove( void *dest, const void *src, size_t count );

​ 我们发现 memmove 函数的参数和返回值与 memcpy 函数一模一样。没错,memmove 函数和 memcpy 函数的功能一样,也是从 src 的位置开始向后复制 count 个节的数据到 dest 的内存位置,并返回 dest 的首地址。

​ 那么它们有什么不同呢❓❓❓

memmove 函数和 memcpy 函数的差别就是,memmove 函数的源内存块和目标内存块是可以重叠的,而 memcpy函数的源内存块和目标内存块是不可以重叠的。

​ 举个例子,比如我们要将 arr 数组中的 1,2,3,4 拷贝到 3,4,5,6 的位置上去,让 arr 数组变为 1,2,1,2,3,4,7,8,9,10

int main()
{
    int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    memcpy(arr + 2, arr, 16);

	for (int i = 0; i < 10; ++i)
		printf("%d ", arr[i]);
	return 0;
}

// 运行结果
1 2 1 2 1 2 7 8 9 10

在这里插入图片描述

​ 我们可以看到,此时源内存块和目标内存块就发生了重叠。我们先把 1,2 拷贝放到了 3,4 的位置,但当我们想把 3,4 拷贝放到 5,6 的位置的时候发现 3,4 位置上放的数字变成了 1,2 所以就把 1,2 拷贝放到了 5,6 的位置,于是数组就变成了 1,2,1,2,1,2,7,8,9,10

memmove 函数便可以很好的解决这个问题。所以当源内存块和目标内存块发生重叠的时候,就得使用 memmove 函数处理。

​ ☢️注意:现在的编译器和 memcpy 的实现已经解决了这个问题,但是我们还是会介绍 memmove 函数!

2、模拟实现

​ 其实这个模拟实现和上面我们写的 my_memcpy 是一样的,具体参考上面!

void* my_memmove(void* dest, const void* src, size_t count)
{
    if (dest == NULL || src == NULL)  // 检查指针是否为空
        return NULL;

    char* pDest = (char*)dest;     
    const char* pSrc = (const char*)src;

    if (pDest > pSrc && pDest < pSrc + count)  // 检查是否重叠,也就是dest在src要复制空间的范围内
    {
        // 重叠要反向拷贝
        for (size_t i = count - 1; i >= 0; --i)
            pDest[i] = pSrc[i];
    }
    else
    {
        for (size_t i = 0; i < count; ++i)
            pDest[i] = pSrc[i];
    }
    return dest;
}

Ⅲ. memcmp – 内存比较

1、函数介绍与使用

#include <string.h>
int memcmp( const void *buf1, const void *buf2, size_t count);

​ 该是一个用于 比较两个内存块大小的函数。它会比较从 buf1buf2 指针开始的 count 个字节,当 buf1 大于 buf2 的时候返回一个大于 0 的数;当 buf1 等于 buf2 的时候返回 0;当 buf1 小于 buf2 的时候返回一个小于 0 的数。

​ 举个例子:

int main()
{
	int arr1[] = { 1, 2, 3, 4 };
	int arr2[] = { 1, 2, 4, 5 };
	int ret1 = memcmp(arr1, arr2, 8);
	int ret2 = memcmp(arr1, arr2, 9);
	printf("%d\n%d\n", ret1, ret2);

	int arr3[] = { 1, 2, 4, 5 };
	int ret3 = memcmp(arr2, arr3, 12);
	printf("%d", ret3);
	return 0;
}

// 运行结果:
0
-1
0

​ 在VS编译器下,内存采用的是小端存储方式,arr1arr2 在内存中的存储形式如图所示:

在这里插入图片描述

​ 所以,当比较字节数为 8 时,返回值为 0;当比较字节数为 9 时,返回值为一个负数。

2、模拟实现

int my_memcmp(const void* s1, const void* s2, size_t n)
{
    if (s1 == NULL || s2 == NULL)  // 检查指针是否为空
        return 0;

    // 转化为unsigned char类型来表示内存中的每个字节,以避免符号扩展的问题
    const unsigned char* p1 = (const unsigned char*)s1;
    const unsigned char* p2 = (const unsigned char*)s2;
    for (size_t i = 0; i < n; ++i)
    {
        if (p1[i] != p2[i])
            return p1[i] > p2[i] ? 1 : -1;
    }
    return 0; // 相等直接返回0
}

Ⅳ. memset – 内存设置

1、函数介绍与使用

#include <string.h>
void *memset( void *dest, int c, size_t count);

将内存块的某一部分设置为特定的字符,设置时候是一个字节一个字节地设置的。

  • dest:开始设置内存的起始位置。
  • c:要将内存设置成的字符。
  • count:从起始位置开始需要设置的内存的字节数。

​ 举个例子:

int main()
{
	char arr[] = "hello world!";
	memset(arr, '*', 5);
	printf("%s", arr);
	return 0;
}

// 运行结果:
***** world!

2、模拟实现

void* my_memset(void * ptr, int value, size_t num) 
{
    unsigned char* p = (unsigned char*)ptr;
    unsigned char v = (unsigned char)value;
    size_t i;
    for (i = 0; i < num; i++) 
    {
        p[i] = v;
    }
    return ptr;
}

​ 该实现首先将 void 指针 ptr 强制转换为 unsigned char 指针 p,以便能够按字节进行访问。然后将 value 强制转换为 unsigned char 类型 v,确保只保留低 8 位。接下来,使用一个循环逐个将内存区域中的字节设置为 v。最后,返回原始的指针 ptr

​ 需要注意的是,memset 的实现可能因编译器和平台而异,上述实现只是一种常见的实现方式。
在这里插入图片描述

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

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

相关文章

解析OVN架构及其在OpenStack中的集成

引言 随着云计算技术的发展&#xff0c;虚拟化网络成为云平台不可或缺的一部分。为了更好地管理和控制虚拟网络&#xff0c;Open Virtual Network (OVN) 应运而生。作为Open vSwitch (OVS) 的扩展&#xff0c;OVN 提供了对虚拟网络抽象的支持&#xff0c;使得大规模部署和管理…

解密AIGC三大核心算法:GAN、Transformer、Diffusion Models原理与应用

在当今数字化时代&#xff0c;人工智能生成内容&#xff08;AIGC&#xff09;技术正以前所未有的速度改变着我们的生活和工作方式。从创意无限的文本生成&#xff0c;到栩栩如生的图像创作&#xff0c;再到动听的音乐旋律&#xff0c;AIGC的魔力无处不在。而这一切的背后&#…

艾体宝干货丨网络故障排除基本指南

一、确保网络可视性以有效排除故障 有效的网络故障排除要求对穿越网络的数据具有完全的可见性&#xff0c;以便迅速识别和解决潜在问题。本指南深入探讨了一种结构化的网络分析方法&#xff0c;旨在提高故障排除的效率。首先&#xff0c;提出正确的问题至关重要&#xff0c;它…

汽车免拆诊断案例 | 2007 款法拉利 599 GTB 车发动机故障灯异常点亮

故障现象  一辆2007款法拉利599 GTB车&#xff0c;搭载6.0 L V12自然吸气发动机&#xff08;图1&#xff09;&#xff0c;累计行驶里程约为6万km。该车因发动机故障灯异常点亮进厂检修。 图1 发动机的布置 故障诊断 接车后试车&#xff0c;发动机怠速轻微抖动&#xff0c;…

浪潮海岳 UploadListFile文件上传致RCE漏洞

一、漏洞简介 浪潮云财务系统的/cwbase/EP/ListContent/UploadListFile.ashx接口存在任意文件上传漏洞&#xff0c;未经身份验证的攻击者可以通过该漏洞上传恶意脚本文件&#xff0c;从而控制目标服务器。 二、漏洞影响 三、网络测绘&#xff1a; fofa: body"/cwbase/w…

高等数学学习笔记 ☞ 不定积分的积分法

1. 第一换元积分法 1. 基础概念&#xff1a;形如的过程&#xff0c;称为第一换元积分法。 2. 核心思想&#xff1a;通过对被积函数的观察(把被积函数的形式与积分表的积分公式进行比较)&#xff0c;把外部的部分项拿到的内部(求原函数)&#xff0c; 然后进行拼凑&#xff0c;…

Spring Boot 整合 Shiro详解

文章目录 Spring Boot 整合 Shiro详解一、引言二、整合步骤1、创建项目并引入依赖2、配置Shiro2.1、自定义Realm2.2、配置SecurityManager和ShiroFilterFactoryBean 三、使用示例四、总结 Spring Boot 整合 Shiro详解 一、引言 在现代的Web应用开发中&#xff0c;用户认证和授…

win10 Outlook(new) 企业邮箱登录 登录失败。请在几分钟后重试。附移除办法

windows系统经常弹出使用Outlook(new&#xff09;&#xff0c;自动切过去。 但是登录企业的内网邮箱&#xff0c;折腾了好几次都使用不了。排查网络等问题&#xff0c;在社区找到了答案。 推出一年多不支持企业账户&#xff0c;所以之前的折腾都是浪费时间。 因为这个答案不太…

tomcat状态一直是Exited (1)

docker run -di -p 80:8080 --nametomcat001 你的仓库地址/tomcat:9执行此命令后tomcat一直是Exited(1)状态 解决办法&#xff1a; 用以下命令创建运行 docker run -it --name tomcat001 -p 80:8080 -d 你的仓库地址/tomcat:9 /bin/bash最终结果 tomcat成功启动

Golang Gin系列-1:Gin 框架总体概述

本文介绍了Gin框架&#xff0c;探索了它的关键特性&#xff0c;并建立了简单入门的应用程序。在这系列教程里&#xff0c;我们会探索Gin的主要特性&#xff0c;如路由、中间件、数据库集成等&#xff0c;最终能使用Gin框架构建健壮的web应用程序。 总体概述 Gin是Go编程语言的…

实现linux硬盘smart检测

一、下载交叉编译libatasmart库 下载链接&#xff1a;https://www.linuxfromscratch.org/blfs/view/svn/general/libatasmart.html libatasmart库编译依赖libudev库&#xff0c;交叉编译器前先准备依赖的libudev: 设置libudev的环境变量&#xff0c;并通过configure编译文件生…

【GIS操作】使用ArcGIS Pro进行海图的地理配准(附:墨卡托投影对比解析)

文章目录 一、应用场景二、墨卡托投影1、知识点2、Arcgis中的坐标系选择 三、操作步骤1、数据转换2、数据加载3、栅格投影4、地理配准 一、应用场景 地理配准是数字化之前必须进行的一项工作。扫描得到的地图数据通常不包含空间参考信息&#xff0c;需要通过具有较高位置精度的…

模型 多元化思维(系统科学)

系列文章分享模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。融合多学科知识&#xff0c;全面解决问题。 1 多元化思维模型的应用 1.1 完美日记的私域流量运营 完美日记作为美妆行业的新兴品牌&#xff0c;通过多元化的思维模型在私域流量运营中取得了显著成功。…

剧本杀门店预约系统开发,门店如何走下去?

近几年来&#xff0c;剧本杀行业经历了大浪淘金&#xff0c;行业进入到创新发展时期&#xff0c;如何在市场中占领一席之地成为了商家探讨的问题。 剧本杀作为一种社交游戏方式&#xff0c;深受年轻人的关注&#xff0c;不仅可以体验游戏的乐趣&#xff0c;还可以满足各种社交…

openharmony标准系统方案之瑞芯微RK3568移植案例

标准系统方案之瑞芯微RK3568移植案例 ​本文章是基于瑞芯微RK3568芯片的DAYU200开发板&#xff0c;进行标准系统相关功能的移植&#xff0c;主要包括产品配置添加&#xff0c;内核启动、升级&#xff0c;音频ADM化&#xff0c;Camera&#xff0c;TP&#xff0c;LCD&#xff0c…

Linux的常用命令(三)

目录 六、网络通信命令 1.网络通信命令ping 2.网络通信命令ifconfig 七、系统命令 1. 系统命令shutdown 2. 系统命令reboot 八、vi编辑器 六、网络通信命令 1.网络通信命令ping 命令名称&#xff1a;ping 命令所在路径&#xff1a;/usr/sbin/ping 执行权限&#xff…

STM32+W5500+以太网应用开发+003_TCP服务器添加OLED(u8g2)显示状态

STM32W5500以太网应用开发003_TCP服务器添加OLED&#xff08;u8g2&#xff09;显示状态 实验效果3-TCP服务器OLED1 拷贝显示驱动代码1.1 拷贝源代码1.2 将源代码添加到工程1.3 修改代码优化等级1.4 添加头文件路径1.5 修改STM32CubeMX工程 2 修改源代码2.1 添加头文件2.2 main函…

Pytorch|YOLO

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、 前期准备 1. 设置GPU 如果设备上支持GPU就使用GPU,否则使用CPU import torch import torch.nn as nn import torchvision.transforms as transforms im…

2025.1.15——六、SQL结构【❤sqlmap❤】

一、打开靶机&#xff0c;整理已知信息 查看页面信息&#xff0c;提示”MySQL结构”&#xff0c;所以为sql注入&#xff0c;两种思路&#xff1a;①手工注入&#xff1b;②sqlmap 二、手工注入解题 step 1&#xff1a;查看注入类型 键入&#xff1a;1 键入&#xff1a;1键入…

螺旋矩阵探讨

文章目录 54.螺旋矩阵59.螺旋矩阵II 54.螺旋矩阵 59.螺旋矩阵 II 54.螺旋矩阵 总体的思路分析&#xff1a; 顺时针&#xff0c;先遍历右边&#xff0c;再下面&#xff0c;再往左&#xff0c;再向上&#xff0c;然后再缩小一圈范围即可 原本的代码情况 class Solution:def spi…