片头
在之前的两篇文章中,我们学习了字符串函数,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个函数木有讲,希望看完这篇文章能对友友们有所帮助!!!
求点赞收藏加关注 ! ! !
谢谢大家 ! ! !