相关文章
使用内存库函数实际上要包含string.h头文件,这个大伙要注意。
1. 模拟 memcpy 内存拷贝
两个指针的指向必须是两块互相独立的内存区域,即两个不同的数组。
dest空间必须比src空间大;
bytes表示要从src拷贝到dest的字节数。
// void* 通用的泛型编程,可以接收任何指针
void* my_memcpy(void* dest, const void* src, size_t bytes) {
assert(dest && src);
if (dest == src) {
return dest;
}
void* t = dest;
while (bytes--) {
// 不清楚void*接收的是什么类型指针,直接char*一个个字节拷贝。
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
/*
((char*)dest)++;
((char*)src)++;
---------------
++((char*)dest);
++((char*)src);
这两种写法换成c++都不行
*/
}
return t;
}
int main() {
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
int arr2[] = { 1, 2, 3, 4};
my_memcpy(arr1 + 4, arr2, sizeof(int) * 4);
for (int i = 0; i < 8; i++) {
printf("%d ", arr1[i]);
}
return 0;
}
对于标准的memcpy,如果不进行完善实际上是有问题的:假设有一个数组 int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 },指针dest和指针src两个指针指向同一个数组内存,并且以字节为单位进行内存移动,会出现几种情况:
1.dest和src指向同一个地址,如my_memcpy(arr, arr, 4),等于啥也没动;
2.dest指向地址值大于src
2.1 如my_memcpy(arr + 4, arr, 16),也就是将5 6 7 8改成1 2 3 4,结果为1 2 3 4 1 2 3 4;
-----------------------------------------------------
2.2 如my_memcpy(arr + 2, arr, 16),也就是将3 4 5 6改成1 2 3 4,即 1 2 1 2 3 4 7 8
但结果会变成1 2 1 2 1 2 7 8,因为原来3 4位置被改成了1 2。
3.dest指向地址值小于src
3.1 如my_memcpy(arr, arr + 4, 16),也就是将1 2 3 4改成5 6 7 8,结果为5 6 7 8 5 6 7 8;
-----------------------------------------------------
3.2 如my_memcpy(arr, arr + 2, 16),也就是将1 2 3 4改成3 4 5 6,结果为 3 4 5 6 5 6 7 8。
到这里会发现,也就只有2.2的结果不是我们想要的,这是因为自己实现的memcpy并不不完善,如果是string.h库函数中的memcpy则不存在这个问题。
本来是要将3 4 5 6改成1 2 3 4,结果改成了1 2 1 2。
事实上对于memcpy函数,C语言标准定义的是两个指针指向的内存位置不能是同一块区域,但显然对于vs2022的编译器而言是将memcpy完善了。但我们使用时还是尽量不要将两个指针指向同一个数组内的元素地址,毕竟要考虑到其它编译器并不一定完善。
画图分析2.2 my_memcpy(arr + 2, arr, sizeof(int) * 4):
图中每个格子代表arr数组中的一个元素,每个元素四个字节。我们利用这个简单的图分析上面模拟实现memcpy的代码,不难看出实际上拷贝是从前往后进行拷贝的,也就是从src、dest的起始位置开始往后拷贝。当拷贝完8个字节后,就变成了下面的样子:
这时的3和4早已被拷贝成了1和2,3和4不存在了。那么5和6就无法被拷贝成3和4了,自然而然也变成了1和2。
而解决这个问题,使用库函数memmove最好,对于这个函数,C语言的使用标准是这样的:两个指针既可以指向同一块内存区域,也可以像memcpy一样,两个指针指向不同内存区域。
2. 模拟 memmove 内存移动
如果自己实现memmove,通过上面例子出现的问题(同一块数组内存区域),如果要解决该问题,要考虑到的情况实际上也就是从前往后还是从后往前拷贝的问题,这个得由dest和src的地址大小比较后决定。
就对于上面模拟实现memcpy的问题,如果是从后往前拷贝,比如把6改成4,再把5改成3,再把3改成2,把2改成1,互不影响那么问题迎刃而解,但如果大伙认为真这么简单那就打错特错了。
对于内存而言以字节为单位,1个整型4个字节,我们实际上是要从最后一个字节开始往前一个个字节拷贝。arr数组的内存布局如下:
每一格都是1个字节,四格则构成一个完整的整型数据,也就是arr数组中的一个元素。格子中的值是用十六进制表示的,至于为什么是倒着存储的,这是因为当前机器以小端字节序存储数据(详细了解请看本篇文章最上面的 相关文章位置,第一个链接中的文章有解释)。
则对于dest地址值大于src的情况,可以这样拷贝:
而对于dest地址值小于src的情况,照常从前往后拷贝:
//模拟memmove(两个指针的指向可以是两块互相独立内存,也可以是同一块内存)
void* my_memmove(void* dest, const void* src, size_t bytes) {
assert(dest && src);
void* t = dest;
// 从后面最后一个字节往前,将6改成4,再将5改成3,4改成2,3改成1,解决上面memcpy 2.2中的问题
if (dest > src) {
while (bytes--) {
*((char*)dest + bytes) = *((char*)src + bytes);
}
}
else if (dest < src) {
while (bytes--) {
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
return t;
}
int main() {
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
my_memmove(arr, arr + 2, sizeof(int) * 4);
for (int i = 0; i < 8; i++) {
printf("%d ", arr[i]);
}
return 0;
}
成功把3 4 5 6改成1 2 3 4!
把1 2 3 4改成3 4 5 6也没问题!