C语言菜鸟成长之旅:《内存操作函数篇》

前言

本篇博客主要介绍常用的4个内存操作函数:
1.不重叠内存拷贝函数 – memcpy()
2.重叠内存拷贝函数 – memmove()
3.内存比较函数 – memcmp()
4.内存填充函数 – memset()

使用这4个函数均需要包含头文件string.h

1. 不重叠内存拷贝函数 – memcpy()

1.1 函数功能介绍

函数memcpysource指向的内存空间的前num个字节的数据复制到destination指向的空间中。
函数数功能图解

1.2 参数及返回值介绍

函数原型:

void* memcpy ( void * destination, const void * source, size_t num );
  • 参数1:destination – 指向用于存储复制内容的目标空间,类型强制转换为 void* 指针。

  • 参数2:source – 指向要复制的数据源,类型强制转换为 void* 指针,const关键字保证数据源不会被修改。

  • 参数3:num – 要被复制的字节数。

  • 返回值:目标空间地址destination,类型为void*

1.3 使用memcpy拷贝各类型数据

1.3.1 代码

(来自网站cplusplus)

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

struct {
    char name[40];
    int age;
} person, person_copy;

int main()
{
    char myname[] = "张三";

    /* 1. 使用 memcpy 拷贝 字符串 */
    memcpy(person.name, myname, strlen(myname) + 1);
    person.age = 46;

    /* 2. 使用 memcpy 拷贝 结构体数据: */
    memcpy(&person_copy, &person, sizeof(person));

    printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);

    /* 3. 使用 memcpy 拷贝 数组数据 */
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr_copy[10] = { 0 };

    memcpy(arr_copy, arr, sizeof(arr));
	
	printf("arr_copy: ");
    for (int i = 0; i < 10; i++)
        printf("%d ", arr_copy[i]);

    return 0;
}

1.3.2 输出结果

person_copy: 张三, 46
arr_copy: 1 2 3 4 5 6 7 8 9 10

1.3.3 解释

  1. memcpy(person.name, myname, strlen(myname) + 1);
    • 第一个参数person.name是指向待放数据数组首元素的指针变量。
    • 第二个参数myname是指向待拷贝的源头数组首元素的指针变量。
    • 第三个参数为表达式strlen(myname) + 1strlen(myname)表示字符串长度(单位:字节),+1表示将'\0'拷贝过去
  2. memcpy(&person_copy, &person, sizeof(person));
    • 第一个参数&person_copy是待放拷贝数据的结构体空间首地址。
    • 第二个参数&person是待拷贝的源头结构体空间首地址。
    • 第三个参数sizeof(person),表示将结构体person的全部内容拷贝到结构体person_copy(单位:字节)。
  3. memcpy(arr_copy, arr, sizeof(arr));
    • 第一个参数arr_copy是待放拷贝数据的数组首元素地址。
    • 第二个参数arr是待拷贝的源头数据数组首元素地址。
    • 第三个参数sizeof(arr)表示拷贝内容大小为40个字节

在这里插入图片描述

1.4 使用细节

  1. 这个函数在遇到 ‘\0’ 的时候并不会停下来。

在这里插入图片描述
2. 前面讲到过,memcpy是使用在不重叠的内存块之间的拷贝,但是,如果sourcedestination有任何的重叠,那么拷贝的结果又当如何——答案是不知道!
C语言官方文档中未对该状况做出明确定义,所以具体情况得看编译器提供的库函数是如何实现的。

假设使用memcpy进行重叠内存拷贝的预期结果:
在这里插入图片描述
实际运行的结果:
在这里插入图片描述
由于内存重叠,在拷贝过程中内存中的数据会受到改变,因此实际结果并非我们预期说想的结果。

  1. VS里面memcpy超额完成了功能,它能够进行重叠内存的拷贝。也就是使其拥有memmove的功能至于memmove又有什么功能,别急,下一个介绍的函数就是它。

1.5 memcpy函数的模拟实现

#include <assert.h>
void* myMemcpy(void* dest, const void* src, size_t num)
{
    assert(dest && src);

    for (size_t i = 0; i < num; i++)
        ((char*)dest)[i] = ((char*)src)[i];

    return dest;
}

2. 重叠内存拷贝函数 – memmove()

2.1 函数功能介绍

void * memmove ( void * destination, const void * source, size_t num );
  • 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
  • 如果源空间和目标空间出现重叠,就得使用memmove函数处理。

2.2 memmove()的实现原理

memmove很好地解决了memcpy在拷贝过程中源内存空间内容因被覆盖而被改变的问题

上面已经把为何会导致出现问题的原因讲的很详细了,所以这里的重点就放在memmove如何解决这个问题上来——在模拟实现memmove()的过程中进行解答

2.2.1 第一种重叠形式

在这里插入图片描述
从上面的拷贝原理图中看到,从src指向空间的数据被从低地址向高地址拷贝(从前向后拷贝)是行不通的。但是,拷贝的方式又不是只有这一种,为什么不试试从后向前拷贝呢?

图解如下:
在这里插入图片描述
拷贝过程中,即使内存中的数据本身被覆盖了,但是源数据本身该被拷贝的数据却并没有没改变。

2.2.2 第二种重叠形式

在这里插入图片描述
解决了上面第一种内存重叠情况的拷贝后,当出现遇到第二种内存重叠情况时,仔细分析一下不难发现这种情况下,更适合从前向后拷贝。

图解如下:
在这里插入图片描述

2.2.3 总结

在这里插入图片描述
以src为界,
dest < src时, 源空间数据从前向后拷贝。
dest >= src时,源空间数据从后向前靠拷贝。

2.3 模拟实现memmove()

include <assert.h>
void* myMemmove(void* dest, const void* src, size_t num)
{
    assert(dest && src);

    if (dest < src)
    {
        for (size_t i = 0; i < num; i++)
            ((char*)dest)[i] = ((char*)src)[i];
    }
    else
    {
        while(num--)
            ((char*)dest)[num] = ((char*)src)[num];
    }
    return dest;
}

3.内存比较函数 – memcmp()

函数原型

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

3.1 函数功能介绍

字节为单位,比较内存空间ptr1ptr2的前num个字节的数据大小。

  • ptr1指向内存块1,类型为void*,可接受任何类型数据
  • ptr2指向内存块2,类型为void*,可接受任何类型数据
  • num表示待比较字节数
  • 返回值规则如下(图片来自cplusplus):在这里插入图片描述

3.2 代码举例

3.2.1 比较整型数据

在这里插入图片描述

第一次比较1个字节,arr2指向的第1个字节 > arr1指向的第1个字节,返回负数。
第二次预计比较21个字节,但是在第一个字节比较完毕时已返回比较结果

3.2.2 比较字符串(对比strncmp

在这里插入图片描述

从上面的代码例子可以看出,memcmpstrncmp二者之间的功能在大致上是相似的,但是,当字符> 串长度小于num时,strncmp遇到'\0'会结束,而memcmp不会,它继续比较,直到比较完num个字节

4. 内存填充函数 – memset()

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

一言蔽之,函数将ptr指向的内存空间的前num个字节设定为默认值value,最后返回ptr
这个函数比较适合用来初始化一些书写不太方便的变量或者数组。

4.1 初始化malloc开辟的数组

两种初始化方式,使用memset比较方便

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

int main()
{
    int* arr1 = (int*)malloc(sizeof(int) * 10);
    int* arr2 = (int*)malloc(sizeof(int) * 10);
    
    //arr1初始化为全0
    memset(arr1, 0, sizeof(int) * 10);
    
    //arr2初始化为全0
    for (int i = 0; i < 10; i++)
        arr2[i] = 0;

    return 0;
}

4.2 初始化结构体变量

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

struct student{
	int ID;
	char name[20];
};

int main(){
	struct student stu;
	memset(&stu, 0, sizeof(stu));
	printf("%d\t%s", stu.ID, stu.name);
	return 0;
}

4.3 初始化结构体数组

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

struct student{
	int ID;
	char name[20];
};

int main(){
	struct student stus[10];
	memset(stus, 0, sizeof(stus));
	printf("%d\n", stus[0].ID);
	printf("%s", stus[0].name);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值