C语言: 内存函数(上)

片头

在之前的两篇文章中,我们学习了字符串函数,C语言中还有一类库函数叫做内存函数。接下来我们一起学习这类函数吧! 准备好了吗? 开始咯!

内存函数

  • 内存函数的功能和某些字符串函数的功能相似,它们是通过访问地址的方式来操作对象,可应用与任何对象。使用内存函数的时候,需要包含头文件<string.h>

1. memcpy函数

1.1 memcpy函数的用法

memcpy函数用于内存拷贝。它的作用是将指定源地址处的内容复制到指定的目标地址处,即在一段内存中的数据拷贝到另一段内存中。

函数原型为:

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

 其中,参数的定义如下:

  • destination:目标地址的指针,用于存储复制后的数据。
  • source:源地址的指针,指向要复制的数据。
  • num:要复制的字节数。

返回值:

  • 返回目标地址的指针

函数功能:

  • 复制源地址source指向的数据的前n个字节到目标地址destination指向的内存中。

以下几点请注意:

  • memcpy不会对目标内存进行初始化或清除,只是简单地覆盖指定大小的字节
  • 如果源地址和目标地址重叠,则memcpy复制的行为是未定义的。在这种情况下,应该使用memmove函数
  • 使用memcpy时,必须确保目标内存区域destination有足够的空间来容纳要复制的n个字节,否则会导致缓冲区溢出,引发程序错误或安全漏洞。
1.2 memcpy函数的使用
#include<stdio.h>
#include<string.h>
int main() {
	//memcpy函数的使用
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[20] = { 0 };

    //一个整型占4个字节,我要从arr1中拷贝10个元素到arr2中,那就是40个字节
	memcpy(arr2, arr1, 10 * sizeof(int));	
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr2[i]);
	}
	return 0;
}

运行结果为:

1 2 3 4 5 6 7 8 9 10 

1.3 memcpy函数的模拟实现 

思路: 实现一个函数从 src 的位置开始向后复制 num 个字节的数据到 dest 指向的内存位置,(将指定长度的内存内容复制到目标地址),并返回目标地址的起始位置。

代码如下:

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

void* my_memcpy(void* dest, void* src, int num) {
	void* temp = dest;	//用temp将dest指针初始的地址保存下来
	assert(dest && src);//确保dest和src指针有效
	while (num--)		//循环复制内存内容,直到复制了num个字节
	{
		//将源地址的当前位置复制到目标地址的当前位置
		//每复制完一个字节,dest和src指针都向后走一步
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return temp;		//返回temp
}

int main() {
	//memcpy函数的使用
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[20] = { 0 };

//一个整型占4个字节,我要从arr1中拷贝10个元素到arr2中,那就是40个字节
	my_memcpy(arr2, arr1, 10 * sizeof(int));	
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr2[i]);
	}
	return 0;
}

运行结果为:

1 2 3 4 5 6 7 8 9 10 


2.memmove函数 

2.1 memmove函数的用法

memmove函数用于在内存中移动(复制)字节块。它的主要用途是处理源内存块和目标内存块重叠的情况,这是memcpy函数无法做到的。

函数原型为:

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

其中,参数的含义如下:

  • destination:  指向目标内存区域的指针,即要接受源内存内容的位置
  • source: 指向源内存区域的指针,即要复制的内容所在的起始位置
  • num: 要拷贝的字节数

返回值:

  • memmove函数返回指向目标内存区域的指针(destination)

请注意:

  • 使用 memmove 函数时,即使源和目标内存区域重叠,它也能正确复制数据。这是因为 memmove 可能会采用不同的策略来确保数据的一致性和正确性,比如先临时存储源数据,然后再复制到目标位置
  • 目标地址和源地址的类型应为void指针,也可以是其他类型的指针,但需要进行强制类型转换。
  • num参数表示要复制的字节数,需要保证源地址和目标地址的内存块大小都至少为num个字节。
  • memmove函数返回一个指向目标地址的指针,即destination的值,可以用来判断复制是否成功。
2.2 memmove函数的使用
#include<stdio.h>
#include<stdlib.h>

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

	//指针 - 指针 ---> 2个指针之间的元素个数
	//指针 + 元素个数 --->  另外一个指针的起始位置
	//将arr1的"3 4 5 6 7" 拷贝到 "1 2 3 4 5"的位置
	memmove(arr1, arr1 + 2, 5 * sizeof(int));
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr1[i]);
	//输出的结果应为: 3 4 5 6 7 6 7 8 9 10
	}
	return 0;

 运行结果为:

3  4  5  6  7  6  7  8  9  10

为啥呢? 我们一起分析分析 。

这是因为: memmove将数组的从第3个位置开始往后的5个整数(3,4,5,6,7)都拷贝到了数组的开始位置

再来一个例子:

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

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

	//指针 - 指针 ---> 2个指针之间的元素个数
	//指针 + 元素个数 --->  另外一个指针的起始位置
	//将arr1的"1 2 3 4 5" 拷贝到 "3 4 5 6 7"的位置
	memmove(arr1 + 2, arr1, 5 * sizeof(int));
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr1[i]);
		//输出的结果应为: 1 2 1 2 3 4 5 8 9 10
	}
	return 0;
}

运行结果为:

1  2  1  2  3  4  5  8  9  10

为啥结果是这样呢? 我们来分析分析

这是因为: memmove 将数组的前5个整数(1,2,3,4,5) 复制到第3个位置开始的内存

2.3 memmove函数的模拟实现

我们已经知道了memmove函数的使用方法,接下来我们一起来模拟这个函数

我们一起来分析分析:

情况1:  
int arr[] = {1,2,3,4,5,6,7,8,9,10};
    memmove(arr,arr+2,5*sizeof(int));

从3位置开始拷贝: 1->3   2->4   3->5   4->6   5->7   从前往后拷贝成功

从7位置开始拷贝: 5->7   4->6  ,后2个数还可正常拷贝,但是当 3->5 的时候,就出现问题了,本应该是"5"的位置被拷贝过来的"7"覆盖掉了,之前的"5"消失了,因此,从后往前拷贝失败

情况2:
    int arr[] = {1,2,3,4,5,6,7,8,9,10}
    memmove(arr+2,arr,5*sizeof(int));

从1位置开始拷贝: 3->1  4->2 , 前2个数可以正常拷贝,但是当 5->3 的时候,就出现问题了,原本是"3"的位置,现在被拷贝过来的"1"覆盖了,原来的"3"消失了,因此,从前往后拷贝失败

从5位置开始拷贝: 7->5   6->4   5->3   4->2   3->1   从后往前拷贝成功

情况3:
        int arr[] = {1,2,3,4,5,6,7,8,9,10};
        memmove(arr,arr+5,5*sizeof(int));

 

从6的位置开始拷贝:  1->6   2->7   3->8   4->9   5->10   拷贝成功

从10的位置开始拷贝:  5->10   4->9   3->8   2->7   1->6   拷贝成功

情况4:
        int arr[] = {1,2,3,4,5,6,7,8,9,10};
        memmove(arr+5,arr,5*sizeof(int));

 

 从1位置开始拷贝:  6->1   7->2   8->3    9->4   10->5    拷贝成功

 从5位置开始拷贝: 10->5   9->4   8->3   7->2    6->1     拷贝成功      

我们可以根据这些情况得出结论:

如果拷贝的两个内存块不重合(交叉),那么从前往后拷贝或者从后向前拷贝都可以; 

如果拷贝的两个内存块有重合(交叉)的部分,的并且dest的地址小于src的地址,就从首元素开始拷贝;如果dest的地址大于src的地址,就从末尾元素开始拷贝。

代码实现如下:

void* my_memmove(void* dest, void* src, int num) {
	void* temp = dest;	//用临时变量temp将初始dest的地址保存起来
	assert(dest && src);
	//比较dest和src的地址
	if (dest < src) {
	//若dest的地址小于src的地址,从前往后拷贝
		while (num--) //循环num次,每次复制一个字节
		{
			*(char*)dest = *(char*)src;	 //将src指向的字节复制到dest指向的位置
			dest = (char*)dest + 1;		 //将dest指针向后移动一个字节
			src = (char*)src + 1;		 //将src指针向后移动一个字节
		}
	}
	else {
	//dest的地址大于或等于src的地址,从后往前拷贝
	//这样可以避免在复制过程中覆盖还未读取的源数据
		dest = (char*)dest + num - 1;	//将dest指针移动到目标区域的最后一个要复制的字节
		src = (char*)src + num - 1;		//将src指针移动到源区域的最后一个要复制的字节
		while (num--) {
			*(char*)dest = *(char*)src; //将src指向的字节复制到dest指向的位置
			dest = (char*)dest + 1;		//将dest指针向前移动一个字节
			src = (char*)src + 1;		//将src指针向前移动一个字节
		}
	}
	return temp;	//返回临时变量temp
}


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

关于这个函数,还有另外一种实现方法,我们一起来看看吧!

void* my_memmove1(void* dest, void* src, int num) {
	void* temp = dest;					//用临时变量temp将初始dest的地址保存起来
	if (dest < src) {
		//从前往后拷贝
		while (num--) {
			*(char*)dest = *(char*)src;	//将src指向的字节复制到dest指向的位置
			dest = (char*)dest + 1;		//将dest指针向后移动一个字节
			src = (char*)src + 1;		//将src指针向后移动一个字节
		}
	}
	else {
		//从后向前拷贝
		while (num--) {
		//比如传递过来的num为20,进入循环,num变成19
		//每拷贝完一次,num自减一次,直到num为0,跳出循环
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return temp;						//返回临时变量temp
}

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

运行的结果为:

1   2   1   2   3   4   5   8   9   10

 片尾

今天的分享就到这里啦,还有2个函数木有讲,希望看完这篇文章能对友友们有所帮助!!!

求点赞收藏加关注 ! ! !

谢谢大家 !  !  !

  • 46
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值