C语言内存函数
《C语言内存函数解析与实现》摘要: 本文深入探讨了C语言中memcpy和memmove两个核心内存函数的原理与应用。memcpy实现了高效的内存拷贝,但存在源/目标内存重叠时的数据覆盖风险;memmove则通过分情况处理(从前向后或从后向前拷贝)解决了重叠问题。文章通过图解和代码示例演示了内存重叠场景下的异常现象,并提供了两个函数的模拟实现:memcpy采用逐字节正向拷贝,memmove则根据地址
C语言内存函数
1.前言
在C语言的学习与实战中,内存操作是贯穿始终的核心知识点,也是区分新手与进阶开发者的关键门槛。内存函数作为直接操作内存的“工具集”,不仅是C语言高效性的体现,更是我们编写可靠、健壮代码的基础——从简单的内存拷贝、填充,到复杂的内存比较、分配,每一个内存函数的合理使用,都直接影响程序的性能、安全性与可维护性。接下来就让我们来了解学习一下C语言中常见的内存函数。
2.memcpy的使用及模拟实现
2.1 memcpy的标准库模型
void *memcpy(void *destination, const void *source, size_t num);
2.2 memcpy的核心功能
memcpy会按字节为单位将 souce 指向的 num 个字节复制到 destination 中,不关心数据类型(char,int,结构体等都可以)
2.3 memcpy的使用
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 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;
}

通过运行,我们可以清楚的观察到这个函数的作用,它将 arr1 的前 20 个字节(也就是前 5 个数字1 2 3 4 5)复制到了 arr2 中。
2.4memcpy的模拟实现
要想完成对memcpy的模拟实现,我们首先来分析一下这个函数,它是从 source(后用 src 代指) 开始的 num 个字节的数据复制到 destnation(后用 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;
}
接下来让我们一起来分析一下这段代码的实现。
- assert 函数的使用是为了保证传入的指针不为 NULL
- void* ret = dest;是为了让函数模拟实现后有参数返回(即 dest 的起始位置)。
- 接下来进入循环,num–,是为了保证循环能进行 num 次,保证从 src 开始的 num个字节数据都能参与循环。
- *(char *)dest = *(char *)src; 这段代码是为了将 src 中的每一个字节内容复制到 dest 中
- 但是我们可以思考一下为什么不能写成 *dest = *src ?究其根本是因为我们传参时默认传入的是 void 类型的指针,而void 类型的指针不能解引用,void 没有类型,没有大小,当我们对 void * 类型指针解引用时,编译器不知道要读取几个字节,不知道取出来的数据是什么类型,因此不能直接写成 *dest = *src。
- 那么我们为什么要将 dest 和 src 强制转化为(char*)类型的指针,而不能转化为其他类型(如 int *)类型的指针呢?这是因为 memcpy 它是按一个一个字节来复制的,char * 类型的指针刚好符合这一特性,它每次只会将 src 中一个字节的内容复制到 dest 中,这刚好满足我们的需求。
- 进行完一次赋值操作后,接下来我们该让 dest 和 src 指向下一个字节所在地址。那么为什么不能用 dest++ 和 src++ 而是使用上述代码所呈现的 dest = (char*)dest + 1和 src = (char*)src + 1,原因其实和上面第5 .6 点一样void* 直接++的话编译器不知道该将指针后移多少个字节,因此需要将其强制类型转换为 char*
- 那么为什么“=”前面的指针不需要强制类型转换,这是因为指针本身存储的是地址,地址之间本身就可以相互赋值,“=” 后面将dest(src)的指针所指向的地址向后偏移一个字节然后再赋值给dest(src)。
- 最后将函数的返回值返回。
综上我们就实现了对函数memcpy的模拟实现。
3.memmove的使用及模拟实现
3.1 memmove的标准库模型
void * memmove ( void * destination, const void * source, size_t num );
3.2 memmove的核心功能
memmove 的核心功能是从源内存块拷贝指定字节数的数据到目标内存块,且能安全处理「源和目标内存区域重叠」的情况。
3.3 内存区域重叠
从上述对 memmove 核心功能的描述我们可以看出,这个函数的功能基本上和 memcpy 的功能一样,那么既然有了 memcpy 这个函数,那么为社么还会有memmove 这个函数,这就要涉及到内存区域重叠了。
那么什么是内存区域重叠呢?接下来我们通过一个图来观察一下
假设创建一个整型数组,数组里存放从 0 ~ 10 这10个数字
现在我们需要将绿色方框里的数据(3 ~ 7)拷贝到红色方框(1 ~ 5)中,如果按照我们上述 memcpy 的功能来操作会是什么情况,接下来让我们通过一段代码来观察。
#include<stdio.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 arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr, arr+2, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

现在我们需要将红色方框里的数据(1 ~ 5)拷贝到绿色方框(3 ~ 7)中,如果按照我们上述 memcpy 的功能来操作会是什么情况,接下来让我们通过一段代码来观察。
#include<stdio.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 arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr+2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
乍一看,最后的输出结果应该是1 2 1 2 3 4 5 8 9 10,但实际上是什么结果呢?让我们通过运行来观察一下。
我们发现最后的结果与我们的猜想大不相同,为什么会出现这种情况呢?接下来让我们通过对代码的分析来探索一下。
memcpy是将 src 的内容从左到右按字节依依复制到 dest 中,上述代码的 src 是 arr,dest 是arr+2,然后他们按字节一 一复制。接下来让我们先观察 1 和 2 的复制。
原本下标为 2 的位置变成 1 ,下标为 3 的位置变成 2 ,这样看的话好像确实没有问题,但是如果再接着复制下去问题就暴露出来了。让我们接着分析,按理说 3 应该复制到下标为 4 的位置,但实际上由于下标为 2 的位置已经变成了 1,所以下标为 4 的地方会被赋值为 1.下标为 5 的地方会被赋值为 2.以此类推 最后得到上述结果1 2 1 2 1 2 1 8 9 10.
上述例子中红色方框的数据与绿色方框的数据重合,这就是内存区域重叠。
3.4 memmove的使用
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr+2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

memmove就是为了当出现内存数据重叠时,进行对内存数据的复制。
3.5 memmove的模拟实现
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;
}
memmove 主要解决的就是上述内存区域重叠的问题,那么该如何解决这个问题呢?
通过观察我们可以发现,当 src 在 dest 后面时,使用 memcpy 能够成功复制,当 src 在 dest 前面时,使用memcpy 无法成功复制,因此我们可以通过分析解决将 src 放到 dest 后面和将 src 放到 dest 前面这两种情况下内存的复制来实现memmove 的模拟实现。
当 dest < src 时,情况与 memcpy 所处理的情况一样,因此我们可以直接套用模拟实现 memcpy 时的代码。
当 dest >= src 时,通过观察我们可以发现从前往后复制的话,难免会出现内存区域重叠的问题,那么我们可以使用另一种方法,即从后往前复制,这样的话就不会有问题了。
接下来我们分析这部分代码该如何实现,因为我们的想法是让它从后往前复制,那么我们就可以将 src 从最后一个字节开始依次从后往前直到第一个字节全部复制到dest中。根据循环 while(num–),因此我们可以依据 num 来设计代码。
先将 dest 和 src 转化为 char* 类型的指针,由于 num 已经自减过一次1,因此此时 dest 和 src 加上 num 时,刚好指向的是各自最后一个字节的内容,然后解引用,再将 src 的内容复制到 dest 中,由于在循环条件中 num 在不断自减,因此不需要其他额外的条件来使得 dest 和 src 指向前面的字节,可以直接使用 num 来实现,到此 memmove 的模拟实现就完成了。
4.提醒
1.在某些编译器(如 vs)中 memcpy 也能实现 memmove 的能力,可以解决内存区域重叠的问题,但并不建议使用 memcpy 来解决。
2.为了避免混淆使用,建议在处理两个不同内存区域时使用 memcpy 来复制,在处理同一个内存区域时,使用 memmove 来复制,或者无论哪种情况都使用 memmove 来复制。
更多推荐



所有评论(0)