0基础C语言积跬步之内存函数

目录

简单介绍

[一、memcpy 使用和模拟实现](#一、memcpy 使用和模拟实现)

[(1)memcpy 的使用](#(1)memcpy 的使用)

[(2)memcpy 的模拟实现](#(2)memcpy 的模拟实现)

[二、memmove 使用和模拟实现](#二、memmove 使用和模拟实现)

[(1)memmove 的使用](#(1)memmove 的使用)

[(2)memmove 的模拟实现](#(2)memmove 的模拟实现)

[三、memset 函数的使用](#三、memset 函数的使用)

[四、memcmp 函数的使用](#四、memcmp 函数的使用)


简单介绍

mem的全拼是memory,在计算机里专门翻译叫:**内存、存储器,**我们以mem开头的函数,都是直接对内存操作的函数,我们称之为内存函数,和str开头的函数不同,str开头的函数只能处理字符串,但mem开头的函数可以用于任何类型的数据,下面让我们来看看有哪些内存函数

我们主要有下面这四大C语言内存函数:

  • memcpy:内存拷贝(不支持内存重叠)
  • memmove:内存拷贝(支持内存重叠)
  • memset:内存初始化(清零最常用)
  • memcmp:内存逐字节比较

总结:

  • str 系列函数 :参数是 char*只能处理 char 类型的字符串 ,以 \0 作为结束标志,不能处理 int、结构体等。
  • mem 系列函数 :参数是 void*不区分数据类型,按字节操作内存,char、int、数组、结构体都能用。
  • memcpy、memmove、memset、memcmp 这 4 个内存函数,全部都在 <string.h>

一、memcpy 使用和模拟实现

(1)memcpy 的使用

memcpy函数的原型与作用:

cpp 复制代码
void* memcpy(void* destination, const void* source, size_t num);
  • 函数 memcpy 从 source 的位置开始向后复制 num 个字节的数据到 destination 指向的内存位置
  • 这个函数在遇到 '\0' 的时候并不会停下来。
  • 如果 source 和 destination 有任何的重叠,复制的结果都是未定义的。
  • 需要返回destination的起始地址!!!
  • 使用需要头文件#include <string.h>
cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	memcpy(arr2, arr1, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	printf("\n");
	return 0;
}

这串代码中memcpy函数是将arr1中的前二十个字节的内存,直接整块复制给了arr2,二十个字节也就是五个整型,将arr1的前五个整型复制给了arr2,arr2 剩下的 5 个元素保持初始值0,所以会打印1 2 3 4 5 0 0 0 0 0

运行截图:

对于自己复制自己这种,内存有重叠的数据,我们用memmove来处理

(2)memcpy 的模拟实现

cpp 复制代码
#include <stdio.h>
#include <assert.h>
//memcpy的模拟实现
void* my_memcpy(void* des, const void* src, size_t num)
{
	assert(des && src);
	void* start = des;
	int count = num;
	while (count--)
	{
		*((char*)des) = *((char*)src);
		((char*)des)++;
		((char*)src)++;
	}
	return start;
}
//主函数
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	my_memcpy(arr2, arr1, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	printf("\n");
	return 0;
}

在对于memcpy函数的模拟实现中,我们首先进行了一个assert断言,保证我们的目标和要复制的空间都不是空指针,接着我们创建了一个void* start指针,用于存放任意类型的地址,将des的地址存给我们的strat指针,也用于最后的返回值,因为我们C语言规定,memcpy函数需要返回destination的起始地址,接着我们创建了一个count整型变量,用于对我们复制的字节数进行计数,复制一个字节,count就减1,在我们的while循环中,我们采取一个字节一个字节复制,所以我们将des和src都强制类型转化为char*类型,解引用时刚好访问一个字节,复制完一个字节后,des++,src也++,但只能一个字节一个字节的++,所以也需要强制类型转化为char*类型,然后接着下次复制,直到循环结束,复制完成。

运行截图:

二、memmove 使用和模拟实现

(1)memmove 的使用

memmove函数的原型与作用:

cpp 复制代码
void* memmove(void* destination, const void* source, size_t num);
  • 和 memcpy 的差别就是 memmove 函数处理的源内存块和目标内存块是可以重叠的。
  • 如果源空间和目标空间出现重叠,就得使用 memmove 函数处理。
  • mommove需返回目标空间destination的起始地址
  • 使用需要头文件#include <string.h>
cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    memmove(arr1 + 2, arr1, 20);
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr1[i]);
    }
    printf("\n");
    return 0;
}

这串代码我们复制arr1的前20个字节的内存,也就是前五个整型元素1 2 3 4 5,从我们的arr1+2的地址处开始覆盖,也就是从arr1的第三个元素开始覆盖,结果也就是1 2 1 2 3 4 5 8 9 10

运行截图:

(2)memmove 的模拟实现

cpp 复制代码
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dst, const void* src, size_t num)
{
    void* start = dst;
    assert(dst && src);
    if (dst < src)
    {
        while (num--)
        {
            *((char*)dst) = *((char*)src);
            dst = (char*)dst + 1;
            src = (char*)src + 1;
        }
    }
    else
    {
        while (num--)
        {
            *((char*)dst + num) = *((char*)src + num);
        }
    }
    return start;
}
int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    my_memmove(arr1+2, arr1, 20);
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr1[i]);
    }
    printf("\n");
    return 0;
}

当dst的地址小于src的地址时,我们采取从src的起始地址开始以一个字节的大小从det的起始地址开始复制,也就是从前往后复制,代码和memcpy的复制方式一样,不过多赘述

当dst的地址大于src的地址时,我们采取从src的最后一个字节的地址开始以一个字节的大小从dst的最后一个字节地址开始复制,也就是从后往前复制,(char*)dst + num和(char*)src + num就能找到dst和src的最后一个字节的地址,随着num--,它们的地址刚好也一个字节一个字节往前挪,从后到前逐个字节进行复制

因为mommove需返回目标空间destination的起始地址,所以我们创建了void*类型的指针变量start

运行截图:

三、memset 函数的使用

memset函数的原型与作用:

cpp 复制代码
void* memset(void* ptr, int value, size_t num);
  • 将 ptr指向的内存块的前 num 个字节,逐个字节设置为指定的value值
  • 使用需要头文件#include <string.h>
cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str[] = "hello world";
	memset(str, 'x', 5);
	printf("%s", str);
	printf("\n");
	return 0;
}

将str的前五个字节的值,设置成字符'x',因为这是char类型的数组,所以我们修改的就是前五个字符,结果也就是xxxxx world

运行截图:

那如果是改变整型数组的值,是否还能正确修改呢?我们来看看:

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	int arr[5] = { 0 };
	memset(arr,1,20);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

我们知道一个int类型占4个字节,这里我们数组大小为5,也就是有5个int类型的元素,总共大小为20个字节,所以我们在memset中选择将前20个字节都修改成1,希望能将数组中的元素都变成1,但真实情况真的是这样子的吗?

我们来看看运行截图:

可以看到,并没有出现我们想要的结果,为什么会这些这种结果,我们来调试内存看看:

上面这个图就是我们数组所开辟的二十个字节的内存空间,可以看到,当我们将前20个字节直接都设置为1时,在内存中存储的每一个字节都改为了1,这样我们随便拿出一个arri,里面存的都是0x01010101,换算成十进制就是16843009,所以才会出现上面的那种效果

四、memcmp 函数的使用

memcmp函数的原型与作用:

cpp 复制代码
int memcmp(const void* ptr1, const void* ptr2, size_t num);
  • 比较从ptr1和ptr2指针指向的位置开始,向后的num个字节
  • 以字节为单位进行比较,不关注数据类型
  • 使用需要头文件#include <string.h>
返回值 含义
0 前 num 个字节 完全相同
> 0 ptr1 大于 ptr2
< 0 ptr1 小于 ptr2
cpp 复制代码
#include <string.h>
#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,7 };
	int arr2[] = { 1,2,3,4,5 };
	int n = memcmp(arr1, arr2, 17);
	printf("%d\n", n);
	return 0;
}

我们比较arr1和arr2两个数组,如上述代码所示,当我们比较前十七个字节时,我们的返回值会是什么?我们来看看运行截图:

为什么会是1呢,我们来调试一下看看:

这是arr1中在内存中存储的数据

这是arr2中在内存中存储的数据

可以看到,前十六个字节都是相等的,当我们比较到第十八个字节时,arr1里存的是0x07,arr2里存的是0x05,所以arr1>arr2,它们的差值是2,然后因为VS 里的 memcmp 只会返回:-1、0、1,不会返回真实字节差值,所以我们会返回一个1,打印出n的结果就是1

感谢大家的观看,新人求互三,关注我必回关!下章见!

相关推荐
yaoxin5211235 小时前
434. Java 日期时间 API - Period 基于日期的时间段
java·开发语言·python
凡人叶枫5 小时前
Effective C++ 条款30:透彻了解 inlining 的里里外外
linux·开发语言·c++·嵌入式开发·effective c++
noipp5 小时前
推荐题目:洛谷 P10907 [蓝桥杯 2024 国 B] 蚂蚁开会
c语言·c++·算法·编程·洛谷
学逆向的6 小时前
C++纯虚函数
开发语言·c++·网络安全
程序员二叉6 小时前
【JUC】ThreadLocal底层原理|内存泄漏|弱引用|跨线程传递方案
java·开发语言·面试·职场和发展·juc
程序员二叉6 小时前
【JUC】线程池全套深度详解|参数|流程|拒绝策略|调优|异常处理
java·开发语言·jvm·算法·面试·juc
凡人叶枫7 小时前
Effective C++ 条款22:将成员变量声明为 private
linux·开发语言·c++
Qt程序员7 小时前
掌握 Linux 内核调度:从原理到实现(进程篇)
java·开发语言
code bean7 小时前
【LangChain】检索器完全指南:从向量检索到生产级 RAG 架构
java·开发语言·微服务
LabVIEW开发7 小时前
LabVIEW + MATLAB 混合编程:爆炸场测试数据精准采集方案
开发语言·matlab·labview