Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,欢迎欢迎~~
💥个人主页:小羊在奋斗
💥所属专栏:C语言
本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为一些学友们展示一下我的学习过程及理解。文笔、排版拙劣,望见谅。
1、memcpy 的使用和模拟实现
2、memmove 的使用和模拟实现
3、memset 函数的使用
4、memcmp 函数的使用
1、memcpy 的使用和模拟实现
1.1 memcpy 函数的使用
memcpy 前面的 mem 指的是 memmory ,英文单词“记忆”,在C语言中指的是内存。后面要介绍的 memmove、memset 和 memcmp 都是如此。
memcpy 是一个内存拷贝函数,其作用是将一个内存区域内指定的 count 个字节大小的内容拷贝到目标内存空间内。值得注意的是,虽然 memcpy 是一个内存函数,但其是定义在 <string.h>头文件内的。
上面关于 memcpy 函数的作用及其用法的描述还是很好理解的,这里再做一些说明。
(1)为了使 memcpy 函数可以实现对任意类型的内容拷贝,其参数定义为了 void *类型的指针;
(2)跟之前学过的字符串相关的函数一样,memcpy 函数拷贝的目标空间必须是可修改的,而被拷贝的内存区域可用 const 修饰;
(3)当 source 和 destination 有任何重叠的时候,复制的结果都是未定义的,也就是说memcpy 函数不负责重叠内存的拷贝。
这个函数还是比较简单的。
1.2 memcpy 函数的模拟实现
memcpy 函数和 strcpy 函数有相似的地方,所以其实现的逻辑也就应该和 strcpy 函数的模拟实现类似。首先我们需要搞清楚,为了实现对任意类型的内容拷贝,我们将 memcpy 函数的参数及其返回值都设定成了 void * 类型的指针,但是 void * 类型的指针有缺点,不能直接进行解引用,也不能对其进行 +- 操作,那我们就要想一个办法解决这个问题。
其实这个问题我们之前在模拟实现 qsort 函数的时候就有了一个解决办法,就是将其强转为char * 类型的指针,因为 char * 类型的指针指向的对象大小是一个字节,是所有类型中字节大小最小的,不管对象类型是多大字节,只要一个字节一个字节地拷贝,就可以对实现对任意类型的内容拷贝了。
那有了上面的思路,我们就能写出下面的代码:
#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* sour, size_t count)
{
assert(dest && sour);
void* pd = dest;
while (count--)//控制拷贝多少个字节
{
*(char*)dest = *(char*)sour;
((char*)dest)++;
((char*)sour)++;
}
return pd;//返回目标空间的起始地址
}
void text1()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
int* pi = my_memcpy(arr2, arr1 + 2, 20);
for (int i = 0; i < 5; i++)
{
printf("%d ", *(pi + i));
}
printf("\n");
}
void text2()
{
char str1[] = "abcdefghijklmnopqrstuvwxyz";
char str2[20] = { 0 };
char* ps = my_memcpy(str2, str1 + 5, 10);
printf("%s\n", ps);
}
int main()
{
text1();
text2();
return 0;
}
跟之前我们模拟实现 qsort 函数相比,这就是张飞吃豆芽。
上面我们是将一个内存区域的内容拷贝到另一个内存区域,那能不能实现在一个内存区域内的拷贝呢?我们来试一下:
可以看到,当我们在一个内存区域内拷贝,并且内存有重叠的时候,my_memcpy 函数就不能完成我们想要的结果了,这是因为重叠的部分已经被拷贝过来的内容代替,原内容就消失了,当拷贝到重叠的内存区域时,拷贝的还是之前拷贝过来的内容。不过只要内存不重叠,在一个内存区域内拷贝也是可行的。
但是,我们可以看到 memcpy 函数并没有这个问题,那是我们模拟的函数有问题吗?其实不是的,memcpy 函数之所以没有这个问题,是因为在某些系统上,memcpy函数可能会检测是否源内存和目标内存有重叠,并采取一些措施以确保正确的结果。然而,这种行为是不可靠的,不同的编译器或系统的实现方式可能会导致不同的结果。
上面的情况和我们在 字符、字符串函数 中介绍到的用 strcat 函数实现一个字符串自己拼接到自己末尾产生的问题是类似的,同样的 strcat 函数表面上虽然也没有什么问题,但是这种行为也是不可靠的。为了代码的可移植性和安全性,最好还是使用memmove 函数来处理重叠内存的情况。接下来我们就来介绍 memmove 函数。
2、memmove 的使用和模拟实现
2.1 memmove 函数的使用
对比 memcpy 函数,memmove 函数与之是及其相似的,特别的是 memmove 函数操作的对象是可以重叠的,正如它所描述的它会将内容如同先复制到一个临时数组中,这样就解决了目标内存区域的内容被覆盖的问题。
2.1 memmove 函数的模拟实现
那么了解了 memmove 函数的逻辑,模拟实现它也不是什么难事。我们只需要创建一个临时数组过渡就行,于是就得到了下面的代码:
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* sour, size_t count)
{
assert(dest && sour);
void* pd = dest;
int i = 0;
char arr[1000] = { 0 };
for (i = 0; i < count; i++)
{
arr[i] = *(char*)sour;
((char*)sour)++;
}
for (i = 0; i < count; i++)
{
*(char*)dest = arr[i];
((char*)dest)++;
}
return pd;
}
int main()
{
int i = 0;
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
my_memmove(arr1 + 2, arr1, 20);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
是不是很简单呢,这样我们就实现了模拟 memmove 函数的功能。但是上面这种创建临时字符数组的办法有一点不足,因为我们并不能确定被拷贝的内容有多大,所以只能模糊地创建一个比较大的数组,但是这个比较大是多大没办法知道,创建大了浪费,创建小了不够,那有没有什么办法能解决这个问题呢?
2.3 memmove 函数的模拟优化
既然我们并不能确定要创建一个多大的临时数组,那我们干脆放弃创建临时数组的方法另辟奇径。
让我们再回到之前遇到的问题,如果内存重叠时拷贝会将原内容覆盖。那是不是我们拷贝的方法有问题呢?来看:
将红色方框内的内容拷贝到蓝色方框内:
我们发现,从前向后拷贝行不通,因为会覆盖掉还没拷贝的内容;但从后向前拷贝是可行的,并没有出现还没拷贝的内容被覆盖的情况。
将蓝色方框内的内容拷贝到红色方框内:
我们又发现,从后向前行不通,但从前向后是可行的。
而之所以有时需要从前向后拷贝,有时需要从后向前拷贝,是取决于是将前面的内容拷贝到后面,还是将后面的内容拷贝到前面。
前面介绍数组的时候我们说过,数组元素随着下标的增大地址逐渐增大。也就是说,如果上面需要将红色方框内的内容拷贝到蓝色方框内,那么当指针p1小于指针p2时,需要从后向前拷贝;当指针p1大于指针p2时,需要从前向后拷贝。而当两个内存区域没有重叠时,从前向后和从后向前都是可行的。
那么,我们就可以在拷贝之前先比较一下指针dest和指针sour的大小,然后再选择是从前向后拷贝还是从后向前拷贝。
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* sour, size_t count)
{
void* pd = dest;
assert(dest && sour);
if (dest < sour)//从前向后
{
while (count--)
{
*(char*)dest = *(char*)sour;
((char*)dest)++;
((char*)sour)++;
}
}
else//从后向前
{
while (count--)
{
*((char*)dest + count) = *((char*)sour + count);
}
}
return pd;
}
void text1()
{
int i = 0;
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
my_memmove(arr1 + 2, arr1, 20);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
}
void text2()
{
int i = 0;
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
my_memmove(arr1, arr1 + 2, 20);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
}
void text3()
{
int i = 0;
char str1[] = "abcdefghijklmn";
int sz = sizeof(str1) / sizeof(str1[0]);
my_memmove(str1, str1 + 2, 5);
printf("%s\n", str1);
}
void text4()
{
int i = 0;
char str1[] = "abcdefghijklmn";
int sz = sizeof(str1) / sizeof(str1[0]);
my_memmove(str1 + 2, str1, 5);
printf("%s\n", str1);
}
int main()
{
//text1();
//text2();
//text3():
text4();
return 0;
}
这时候我们写的 my_memmove 函数就比较完善了。
其实小伙伴们也能感觉到 memmove 函数完全可以代替 memcpy 函数,而且 memmove 函数不用管内存是否重叠的问题。那 memcpy 函数不就没有存在的必要了吗?其实内存重叠只是一种特殊情况,在确定没有内存重叠的情况下使用 memcpy 函数效率会更高,因为 memcpy 函数没有比较指针大小这一步骤。
当然如果你嫌麻烦始终使用 memmove 函数也是没有什么问题的,就是效率低那么一丢丢而已。
3、memset 函数的使用
memset 函数是用来设置内存的,它的作用是将内存中的值以字节为单位设置成想要的内容。
需要注意的是,memset 函数是以字节为单位设置的,否则会写成下面这种代码:
我们知道整型占4个字节,整数7以16进制表示为:0x07 00 00 00,上面的代码执行过后就变成了:0x01 01 01 01,并没有达到我们想要的效果。所以我们要谨记 memset 函数是以字节为单位一个字节一个字节设置的,并不是以元素为单位的。
4、memcmp 函数的使用
memcmp 函数和 strncmp 函数极其相似,也是比较两个指针指向内容的大小,唯一的区别是 strncmp 只能比较字符串,而 memcmp 可以比较任意类型。和 memset 函数一样 memcmp 也是以字节为单位比较的。
以上所有的函数都是可以操作内存的函数,与前面介绍的字符、字符串函数不同的是内存函数可以操作任意类型的内容。
如果觉得我的文章还不错,请点赞、收藏 + 关注支持一下,我会持续更新更好的文章。