【C语言】内存函数的详细教学和模拟实现

news2024/12/26 10:51:17

🚀write in front🚀
🔎大家好,我是gugugu。希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
🆔本文由 gugugu 原创 CSDN首发🐒 如需转载还请通知⚠
📝个人主页:gugugu—精品博客
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​
📣系列专栏:gugugu的精品博客
✉️我们并非登上我们所选择的舞台,演出并非我们所选择的剧本📩

在这里插入图片描述
vs 启动

前言

上一篇博客里讲到了字符函数和字符串函数,那么在这一篇博客中,我们将另一种常见的函数讲解一下,就是内存函数,内存函数比字符函数和字符串函数更加的广泛,毕竟是针对内存的函数。
在这里插入图片描述

一、内存函数与字符串函数的区别

C语言内存函数,是针对内存块的,不在乎内存中的数据,但是字符串函数时针对字符串的,在乎内存中的数据,只操作字符串,与\0操作符关系密切。

二、memcpy函数

memcpy函数与strcpy函数功能比较相似,都是进行拷贝操作,但是memcpy针对的对象不同。

1、memcpy函数的基本结构

void* memcpy(void * destination ,const void * source,size_t num);
函数有三个参数,分别为起始地址,目标地址和移动的字节的大小,返回值是void*

  • 那么为什么起始地址和目标地址,以及返回值都是void类型呢?

在这里插入图片描述

因为memcpy函数针对的对象是内存空间,而内存空间中储存的数据类型不清楚,有多种可能性,所以直接使用void*类型的指针,在使用时,进行强制类型转换。

另外,在这里补充一点
在上一篇文章里面,很多字符串函数的返回值都是一个指针,这是为什么呢?

在这里插入图片描述

其实,这是为了能够通过返回值去更方便的进行链式访问

2、memcpy函数的模拟实现

在模拟实现memcpy这些内存函数的时候,主要是要注意对void*的强转,这比较巧妙。

这里提供两种方法。大同小异
方法一

#include <stdio.h>
#include <assert.h>

void* my_memcpy(void* ch1, const void* ch2, size_t num)
{
    assert(ch1 && ch2);
    void* ret = ch1;
    int i = 0;
    for (i = 0; i < num; i++)
    {
        *((char*)ch1)++ = *((char*)ch2)++;
    }
    return ret;
}


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

    char ch1[] = "ZZZZZZZZZZZ";
    char ch2[] = "YYYYYYYYY";
    void * ret2=my_memcpy(ch1, ch2, 6);
    printf("%s\n", (char *)ret2);
    return 0;
}

方法二

#include <stdio.h>
#include <assert.h>

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


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

    char ch1[] = "ZZZZZZZZZZZ";
    char ch2[] = "YYYYYYYYY";
    void * ret2=my_memcpy(ch1, ch2, 6);
    printf("%s\n", (char *)ret2);
    return 0;
}

memcpy函数针对的对象是内存空间,所以对整形和字符都可以处理

三、memmove函数

1、memmove函数的优势

memcpy函数在使用时会存在问题,比如目标空间和起始空间发生了重叠,此时使用memcpy函数就会出现问题。
看下面的例子
在这里插入图片描述

#include <stdio.h>
#include <assert.h>

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

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

按照设想,答案应该是 1 2 1 2 3 4 5 0 0 0
但是实际答案是
在这里插入图片描述
是不是没想到?
在这里插入图片描述

这是为什么呢?
主要是在实现的时候,读取到第三个数的时候,本来是3,但是被赋值之后就变成了1,所以第三个数也就成了1,而不是三,后面也是一样。

但是memmove函数可以解决这个问题
在这里插入图片描述

2、memmove函数的模拟实现

像上面实现memcpy一样从前面向后面拷贝出现了问题,那么如果从后面往前面拷贝,又当如何?
在这里插入图片描述

这是就会先将5放到arr[6]上,4放到arr[5]上,依次类推,可以发现,不会出现问题。

但是又有新的问题,如果是memmove(arr,arr+2,20),这又会怎么办呢?

这是从后往前就不行了,就得从前往后拷贝。

聪明的小伙伴,看到这里肯定能够想出解决方案。

  • 当目的地址比起始地址大时,从后往前拷贝
  • 当目的地址比起始地址小时,从前往后拷贝

上代码
在这里插入图片描述

#include <stdio.h>
#include <assert.h>
void* my_memmove(void* ch1, const void* ch2, size_t num)
{
    assert(ch1 && ch2);
    void* ret = ch1;
    if (ch1 > ch2)
    {
        while (num--)//自减操作后num已经是19了
        {
            *((char*)ch1 + num) = *((char*)ch2 + num);//每次自减操作后,num都会少1,向前走了一个字节
        }
    }
    else
    {
        while (num--)
        {
            *(char*)ch1 = *(char*)ch2;
            ch1 = (char*)ch1 + 1;
            ch2 = (char*)ch2 + 1;
        }
    }
    return ret;
}

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

四、memset函数

这个函数比较简单,就不详细讲解了
在这里插入图片描述

1、memset函数的功能

set的意思是设置,我们在这里把它理解成赋值,就是给内存去赋值

先写段代码看看功能吧

#include <stdio.h>

int main()
{
	char ch[20] = "hello world!";
	memset(ch, 'x', 10);
	printf("%s\n", ch);
	return 0;
}

在这里插入图片描述
需要注意的是

memset函数的结构比较特殊
void * memset(void* ptr,int value,size_t num);
第二个参数是int类型,为啥我的例子里面给的是char呢?

是因为char是使用ASCII码值进行操作的。

2、memset函数的模拟实现

比较简单,直接上代码

#include <stdio.h>
#include <assert.h>
void* my_memset(void* ch, int value ,size_t num)
{
	assert(ch);
	void* ret = ch;
	while (num--)
	{
		*(char*)ch = value;
		ch = (char*)ch + 1;
	}
	return ret;
 }
int main()
{
	char ch[20] = "hello world!";
	my_memset(ch, 'x', 10);
	printf("%s\n", ch);
	return 0;
}

在这里插入图片描述
运行成功,yeah

五、memcmp函数

这个函数也比较简单,就是对内存进行比较

1、memcmp函数的基本结构

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

  • 返回值是int 跟strcmp一样
  • 两个指针参数都加上了const ,无法修改内容
  • num是比较的字节数

2、memcmp函数的模拟实现

比较简单,直接上代码
在这里插入图片描述

#include <stdio.h>
#include <assert.h>
int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
	assert(ptr1 && ptr2);
	while (num--)
	{
		if (*(char*)ptr1 == *(char*)ptr2)
		{
			ptr1 = (char*)ptr1 + 1;
			ptr2 = (char*)ptr2 + 1;
		}
		else
			return *(char*)ptr1 - *(char*)ptr2;
	}
	return 0;
}
int main()
{
	char ch1[20] = { 0 };
	char ch2[20] = { 0 };
	gets(ch1);
	gets(ch2);
	int num = 0;
	scanf("%d", &num);
	int ret = my_memcmp(ch1, ch2, 5);
	if (ret > 0)
		printf(">\n");
	else if (ret < 0)
		printf("<\n");
	else
		printf("==\n");
	return 0;
}


ok ,这次的分享到这里就结束了,函数的内容基本上就要告一段落了

今天下午还会有一更哦,敬请关注!!!


!!!!!!!!!!!!!!!!!求关注!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!蹲个一键三连!!!!!!!!!!!!!!!

在这里插入图片描述

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

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

相关文章

全志ARM926 Melis2.0系统的开发指引②

全志ARM926 Melis2.0系统的开发指引② 编写目的4. 编译工具链使用4.1.工具链通用配置4.2.模块的工具链配置4.3.简单的 makefile 5. 固件烧录工具的安装5.1.PhoenixSuit 的安装步骤5.2.检验 USB 驱动安装5.3.使用烧录软件 PhoenixSuit -全志相关工具和资源-.1 全志固件镜像修改工…

Python:操作SQLite数据库简单示例

本文用最简单的示例演示python标准库提供的SQLite数据库进行新增、查询数据的过程。 代码文件app.py # -*- coding: UTF-8 -*- from flask import Flask import sqlite3app Flask(__name__)app.route(/) def hello_world():return Hello World!#创建数据库 app.route(/creat…

Go基础之变量和常量

Go基础之变量和常量 文章目录 Go基础之变量和常量一. 标识符、关键字、内置类型和函数1.1 标识符1.2 关键字1.3 保留字1.4 内置类型1.4.1 值类型&#xff1a;1.4.2 引用类型&#xff1a;(指针类型)1.5 内置函数1.6 内置接口error 二.Go变量命名规范2.1 采用驼峰体命名2.2 简单、…

Python无废话-办公自动化Excel图表制作

openpyxl 支持用Excel工作表中单元格的数据&#xff0c;创建条形图、折线图、散点图和饼图等。 图表制作步骤 在openpyxl模块中创建图表&#xff0c;步骤如下: ①选择一个单元格区域&#xff0c;创建Reference 对象&#xff0c;作为图形数据a)(Value)。 ②创建一个Chart对象…

阿里云ECS服务器上启动的portainer无法访问的问题

如下图&#xff0c;在阿里云ECS服务器上安装并启动了portainer&#xff0c;但是在自己电脑上访问不了远程的portainer。 最后发现是要在网络安全组里开放9000端口号&#xff0c;具体操作如下&#xff1a; 在云服务器管理控制台点击左侧菜单中的网络与安全-安全组&#xff0c;然…

阻塞队列--线程安全问题

之前的队列在很多场景下都不能很好地工作&#xff0c;例如 大部分场景要求分离向队列放入&#xff08;生产者&#xff1a;主要调用offer方法&#xff09;、从队列拿出&#xff08;消费者&#xff1a;主要调用poll方法&#xff09;两个角色、它们得由不同的线程来担当&#xff0…

uboot启动流程-uboot内存分配工作总结

一. uboot 启动流程 _main 函数中会调用 board_init_f 函数&#xff0c;本文继续简单分析一下 board_init_f 函数。 本文继续具体分析 board_init_f 函数。 本文继上一篇文章的学习&#xff0c;地址如下&#xff1a; uboot启动流程-uboot内存分配_凌肖战的博客-CSDN博客 二…

【C语言】浮点数在内存中的存储和读取——底层分析

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是gugugu。希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f194;本文由 gugugu 原创 CSDN首发&#x1f412; 如需转载还请通知⚠…

【12】c++设计模式——>单例模式练习(任务队列)

属性&#xff1a; &#xff08;1&#xff09;存储任务的容器&#xff0c;这个容器可以选择使用STL中的队列&#xff08;queue) &#xff08;2&#xff09;互斥锁&#xff0c;多线程访问的时候用于保护任务队列中的数据 方法&#xff1a;主要是对任务队列中的任务进行操作 &…

MySql运维篇---008:日志:错误日志、二进制日志、查询日志、慢查询日志,主从复制:概述 虚拟机更改ip注意事项、原理、搭建步骤

1. 日志 1.1 错误日志 错误日志是 MySQL 中最重要的日志之一&#xff0c;它记录了当 mysqld 启动和停止时&#xff0c;以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时&#xff0c;建议首先查看此日志。 该日志是默认开启的&a…

[激光原理与应用-71]:光电传感器的工作原理详解:光电效应原理、类型、光信号的光谱、电信号的频谱

目录 前言&#xff1a; 一、光电传感器组成 二、光电元件&#xff1a;光电效应的分类 2.1 外光电效应&#xff1a;逸出效应 2.2 内光电效应&#xff1a;光电导效应 2.3 内光电效应&#xff1a;光生伏特效应&#xff08;电流效应&#xff09; 2.3.1 光电转换元件PD 三、…

云安全之等级保护详解

等级保护概念 网络安全等级保护&#xff0c;是对信息系统分等级实行安全保护&#xff0c;对信息系统中使用的安全产品实行按等级管理&#xff0c;对信息系统中发生的信息安全事件分等级进行响应、处置。 网络安全等级保护的核心内容是&#xff1a;国家制定统一的政策、标准&a…

【ldt_struct】0ctf2021-kernote

前言 题目给的文件系统是 ext4&#xff0c;所以我们只需要将其挂载即可使用&#xff1a; 1、创建一个空目录 2、使用 mount 将其挂载即可 3、使用 umount 卸载即可完成打包 开启了 smap、smep、kaslr 和 kpti 保护&#xff0c;并且给了如下内核编译选项&#xff1a; Her…

SpringBoot大文件上传实现分片、断点续传

大文件上传流程 客户端计算文件的哈希值&#xff0c;客户端将哈希值发送给服务端&#xff0c;服务端检查数据库或文件系统中是否已存在相同哈希值的文件&#xff0c;如果存在相同哈希值的文件&#xff0c;则返回秒传成功结果&#xff0c;如果不存在相同哈希值的文件&#xff0…

GO 中的指针?

本文也主要聊聊在 GO 中的指针和内存&#xff0c;希望对你有点帮助 如果你学习过 C 语言&#xff0c;你就非常清楚指针的高效和重要性 使用 GO 语言也是一样&#xff0c;项目代码中&#xff0c;不知道你是否会看到函数参数中会传递各种 map&#xff0c;slice &#xff0c;自定…

使用正则表达式批量修改函数

贪心匹配&#xff0c;替换中的$1代表括号中的第一组。 使用[\s\S\r]代表所有字符&#xff0c;同时加个问号代表不贪心匹配:

【RP-RV1126】烧录固件使用记录

文章目录 烧录完整固件进入MASKROM模式固件烧录升级中&#xff1a;升级完成&#xff1a; 烧录部分进入Loader模式选择文件切换loader模式 烧录完整固件 完整固件就是update.img包含了所有的部件&#xff0c;烧录后可以直接运行。 全局编译&#xff1a;./build.sh all生成固件…

Java数据结构————优先级队列(堆)

一 、 优先级队列 有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c; 一般出队列时&#xff0c;可能需要优先级高的元素先出队列。 数据结构应该提供两个最基本的操作&#xff0c; 一个是返回最高优先级对象&#xff0c; 一个是添加新的对象。 这种数据结构就是优…

(一)正点原子STM32MP135移植——准备

一、简述 使用板卡&#xff1a;正点原子的ATK-DLMP135 V1.2 从i.mx6ull学习完过来&#xff0c;想继续学习一下移植uboot和内核的&#xff0c;但是原子官方没有MP135的移植教程&#xff0c;STM32MP157的移植教程用的又是老版本的代码&#xff0c;ST官方更新后的代码不兼容老版本…

微信小程序button按钮去除边框去除背景色

button边框 去除button边框 在button上添加plain“true”在css中添加button.avatar-wrapper {background: none}用于去除button背景色在css中添加button.avatar-wrapper[plain]{ border:0 }用于去除button边框