C语言--内存操作函数


首先,从各个内存函数的函数名上看,都有一个mem,其实就是在英语上的对memory的简称,本意是记忆,在计算机体系中翻译为内存的意思,可以了解一下,也方便记忆。那么我们就开始学习以下几个C语言中的内存操作函数

注:与字符串函数类似,内存函数都需要加一个头文件 <string.h>

一、memcpy

memcpy可以理解为memory copy,即内存拷贝的意思,所以它的用处就是将一块内存空间的内容拷贝到另外一个内存空间。
首先我们要知道memcpy函数的各个参数和返回值的类型,如下:

void* memcpy(void* destination, const void* source, size_t num);

我们清楚地看到,memcpy函数有三个参数,返回类型为void*

  1. 第一个参数是 destination ,顾名思义就是目的地,就是要把拷贝的内存放到另一个目的地的内存空间中,目标空间的起始地址,类型是void*。
  2. source,源头的意思,就是要拷贝的内存的起始地址,类型是void*,因为我们不希望要拷贝的内存空间被改变,所以在前面用const修饰。
    memcpy是对内存空间拷贝,不在乎内存中放的是什么类型的数据,所以destination和source的类型都是void*。
  3. num,就是要拷贝内存空间的大小,类型是size_t,以字节为单位
    拷贝结束后,返回的是目标空间(destination指向的空间)的起始地址
    总结它的功能就是:函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置

注:在拷贝内存空间的时候,destination和source两块内存空间可能会有重叠的部分,这时的结果是未定义的,但是用memcpy函数可能也会成功,但是不建议这样使用,如果有重叠的部分,会有memmove内存函数来专门实现

1.1 memcpy的使用

首先初始化两个数组

int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };

然后我们想要把arr1数组中的前5个元素拷贝到arr2数组中,要用到memcpy内存函数,所以参数destination就是arr2,参数source就是arr1,因为num是以字节为单位的内存空间大小,数组元素为int类型,大小为4个字节,所以5个元素总共大小就是20个字节

memcpy(arr2, arr1, 20);

然后我们用for循环打印arr2数组

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

具体使用memcpy内存函数代码如下:

#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]);
	}
	return 0;
}

结果:
在这里插入图片描述

1.2 memcpy的模拟实现

了解了memcpy内存函数,那么我们可以思考一下如何自己设计一个函数来模拟实现memcpy函数
首先我们设计一个函数,函数名为my_memcpy,既然是模拟实现,与memcpy的函数参数要一样,即:

void* my_memcpy(void* dest, const void* src, size_t num)

与上文一样,在主函数中,首先创建两个数组

int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };

然后使用我们的my_memcpy函数

my_memcpy(arr2, arr1, 20);

最后打印

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

那么如何去具体模拟实现my_memcpy呢
首先要保证两个指针参数不能是空指针,保证安全性,所以首先要用assert函数断言一下,需要加头文件<assert.h>

assert(dest && src);

然后想一下memcpy函数是把一块内存空间的内容拷贝到另一个内存空间,要拷贝的内存空间的大小就是参数里的num,那么我们就可以有了一个思路:我们是不是可以把要拷贝的空间大小num分成一个字节一个字节的拷贝,每拷贝一次,num个字节的空间就少一个字节,num- -;所以可以使用while循环

while (num--)
	{}

参数dest和src都分别代表着两块内存空间的起始地址,因为这两个参数的类型是void*,所以不能对其解引用,就需要对其进行强制类型转换,那么强制转换成什么类型呢,我们思考,我们需要对内存空间一个字节一个字节的拷贝,所以将其转换成(char*)类型,然后在解引用拷贝

*(char*)dest = *(char*)src;

拷贝一次后,需要继续向后拷贝,一次跳过一个字节,因为(char*)类型的指针加1一次跳过一个字节,于是

dest = (char*)dest + 1;
src = (char*)src + 1;

所以循环如下:

while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}

那么此时我们已经将内存空间进行了拷贝,函数需要返回目标空间的起始地址,所以就有人说了,直接返回dest不就行了,但是细心的朋友会发现,此时dest经过拷贝过程,已经不在指向之前的目标空间了,所以我们在拷贝循环开始前需要在设置一个指针变量来放dest一开始时的地址,类型是void*

void* ret = dest;

整体函数实现如下

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

具体模拟实现memcpy函数如下:

#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}
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]);
	}
	return 0;
}

结果:
在这里插入图片描述

二、memmove

2.1 memmove的使用

在上文中,我们知道,如果拷贝的两块空间有重叠,那么最好不要用memcpy函数,而是用memmove函数
首先,memmove的参数和返回值与memcpy的个数类型相同,

void * memmove ( void * destination, const void * source, size_t num );

比如,定义一个数组

int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };

我们想把1 2 3 4 5拷贝到3 4 5 6 7 的位置上,两者内存空间上有元素3 4 5内存空间的重叠,我们知道arr数组名代表数组首元素的起始地址,就是第一个元素1的起始地址,那么arr+2,1是int类型元素,+1跳过一个整型类型的大小,则arr+2就跳过2个指向第三个元素3的起始地址,要拷贝5个int类型的大小,则num就是5*sizeof(int)

memmove(arr1 + 2, arr1, 5 * sizeof(int));

然后打印

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

具体使用如下:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr1 + 2, arr1, 5 * sizeof(int));
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

结果:
在这里插入图片描述

2.2 memmove的模拟实现

与memcpy模拟实现同理,设计一个函数my_memmove,与memmove函数相似

void* my_memmove(void* dest, const void* src, size_t num)

于是我们考虑如何实现my_memmove,既然有重叠,那么就有两种情况,一种是dest指向的内存空间是低地址,src指向的是高地址,另一种则反过来,dest是高地址,src是低地址。
当dest是低地址时,比如dest指向1的地址,src指向3的地址,将3 4 5 6 7 拷贝到1 2 3 4 5的位置上
在这里插入图片描述
此时可以用my_memcpy的模拟实现中的从前往后一个一个字节拷贝的方法

if (dest < src)
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}

但是当dest是高地址,src是低地址时,这种方法就不行了,比如,src指向1的地址,dest指向3的地址,把1 2 3 4 5拷贝到3 4 5 6 7上去
在这里插入图片描述
由图可知,当把1拷贝到3上去时,放在3位置的3改变为1了,当将3的位置元素拷贝到5的位置时,拷贝的就不是3,而是1,就是错误的结果。那么如何避免这种错误呢,我们可以类比dest是低地址时的方法,是从前向后一个一个字节拷贝,那么dest是高地址的时候,从后向前拷贝不就行了
在这里插入图片描述
所以当dest是高地址 src是低地址的时候,dest > src时,从后向前拷贝,
那么实现如下:

while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}

先判断num是否为0,不为0,先num–;进入循环后,num就是num-1的值了,则*((char*)dest + num)就表示目标空间最后面的内存空间的地址,((char)src + num)就表示被拷贝空间最后面的内存空间的地址,从后向前拷贝
同理主函数如图所示:

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

模拟代码实现如下:

#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;
	if (dest < src)
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr1 + 2, arr1, 5 * sizeof(int));
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

结果:
在这里插入图片描述

三、memset

3.1 memset的使用

memset函数是用来设置内存的,将内存中的值字节为单位设置成想要的值
参数和返回值如下:

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

ptr是想要改变的内存的起始地址,value是想要设置成的数据,num是想要设置的内存空间的大小
功能:设置从ptr指向的内存空间起始地址开始向后num个字节的内存空间的具体内容

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

int main()
{
	char str[] = "hello world";
	memset(str, 'x', 6);
	printf("%s", str);
	return 0;
}

str数组名表示首元素地址,向后6个字节的内存空间内容设置为’x’
结果:在这里插入图片描述

四、memcmp

4.1 memcmp的使用

memcmp函数是比较内存空间大小的一个内存函数,函数参数返回值形式如下:

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

ptr1和ptr2分别是要比较的两个内存空间的起始地址,num是要比较的你存空间的字节大小
功能:⽐较从ptr1和ptr2指针指向的位置开始,向后的num个字节的内存空间中放的具体内容,如果ptr1指向的内容比ptr2指向的内容返回一个比0大的数字;如果相等返回0;如果ptr1指向的内容比ptr2指向的内容,返回一个比0小的数字(在VS中返回值分别是1, 0,-1)
实现:

#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "abcdefg";
	char str2[] = "abcdmmm";
	int ret =memcmp(str1, str2, 3);
	printf("%d", ret);
	return 0;
}

str1和str2是数组名,分别代表各自数组首元素的地址,向后比较3个字节内存空间的内容,char类型是一个字节,所以比较三个
结果:
在这里插入图片描述
向后比较6个字节,m的ASCII码值比e大

#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "abcdefg";
	char str2[] = "abcdmmm";
	int ret =memcmp(str1, str2, 6);
	printf("%d", ret);
	return 0;
}

结果:
在这里插入图片描述
将str1与str2的内容换一下

#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "abcdmmm";
	char str2[] = "abcdefg";
	int ret =memcmp(str1, str2, 6);
	printf("%d", ret);
	return 0;
}

结果:
在这里插入图片描述
如果是整型比较

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6 };
	int arr2[] = { 1,2,3,4,3,3 };
	int ret =memcmp(arr1, arr2, 16);
	printf("%d", ret);
	return 0;
}

注意int类型是4个字节,所以注意num的大小;
结果:
在这里插入图片描述

但是如果比较了17个字节的话,结果就变为1了

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6 };
	int arr2[] = { 1,2,3,4,3,3 };
	int ret =memcmp(arr1, arr2, 17);
	printf("%d", ret);
	return 0;
}

结果:
在这里插入图片描述

我们知道正整数再内存中的存储是二进制的,比如1的二进制是
00000000 00000000 00000000 00000001
转换为16进制就是(4位转1位)
0x00 00 00 01
同理5和3的16进制分别是
0x00 00 00 05
0x00 00 00 03
那么如果比较17个字节的话,从左到右先多出来的一位字节不是00吗,其实在内存中是倒着存
在VS调试窗口中查看arr1数组的内存
在这里插入图片描述
可以看到5在内存中先存放的是05 00 00 00
所以先是05,而不是00,05比03大所以返回1.

后面博主也会详细讲解数据在内存中的存储,其中有错误和待改进的地方望大家指出和提出建议不吝赐教,感谢阅读!

  • 13
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值