目录
- memcpy(Copy block of memory)使用和模拟实现
- memcpy的模拟实现
- memmove(Move block of memory)使用和模拟实现
- memmove的模拟实现:
- memset(Fill block of memory)函数的使用
- 扩展
- memcmp(Compare two blocks of memory)函数的使用
感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接
🐒🐒🐒个人主页
🥸🥸🥸C语言
🐿️🐿️🐿️C语言例题
🐣🐓🏀python
memcpy(Copy block of memory)使用和模拟实现
代码格式:
void * memcpy ( void * destination, const void * source, size_t num );
memcpy使用要点:
1:函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置
2:这个函数在遇到 ‘\0’ 的时候并不会停下来
3:如果source和destination有任何的重叠,复制的结果都是未定义的
4:由于不知道程序猿会传入什么样的指针,所以我们用void * 表示各种类型的指针,因此我们用void*后可以传入int * ,char * …类型的指针
此外我们还有区分 strcpy和memcpy,一个是拷贝字符,一个是拷贝内存,memcpy拷贝是针对不重叠的拷贝)
我们解释一下重叠的含义:
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
memcpy(arr+2, arr , 16);
return 0;
}
这就是拷贝重叠,arr+2是表示的&a[2],arr是数组名表示首元素的地址,我们用一个图来说明
首先我们将a[0]=0,a[1]=1分别替换a[2]=2,a[3]=3,由于传进去的是地址,所以是永久改变,此时我们的a[2]和a[3]都发生了变化,但是a[2]和a[3]还没有去替换a[4]和a[5]
当我们用a[2]和a[3]去替换a[4]和a[5]时,a[2]和a[3]已经变成了0和1,所以替换a[4]和a[5]的结果也是0和1
只不过有些编译器的结果并不是这样的(比如VS),因为VS中的memcpy约等于memmove,唯一的解释就是程序猿在实现memcpy时的编写不同,所以代码的结果就不同,但是大多数编译器都是前面的结果
代码示例:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 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;
}
代码解析:
这里有两个int类型的数组,arr2中的元素全是0,我们将arr1中的元素复制到arr2中
由于我们传入的arr2和arr1都是数组的数组名,也就是数组首元素的地址,传入的字节数为20,因为一个数组中的元素大小为4个字节,所以相当于传入5个元素到arr2中
之后就从arr2中第一个元素0开始改变,总共改变5个元素,打印的结果如下
memcpy的模拟实现
void* my_memcpy(void* dest, const void* src, size_t sz)
{
assert(dest && src);
while (sz--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return dest;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
代码解析:
因为是拷贝的内存,但是数组中的元素是整形元素,如果我们拷贝的内存大小不是4的倍数(一个整形大小为4个字节),比如7,那么可能就无法拷贝进去
因此我们需要将传入的数组元素地址进行强制类型转换成char,只有这样才能一个字节一个字节的拷贝, * (char)dest = * (char*)src是将dest和src强制转换为char*类型的指针,再解引用,将src的一个字节拷贝给dest**
而dest = (char)dest + 1和src = (char)src + 1都是将两个数组强制类型转换,再通过+1跳到下一个字节,这样我们就可以保证每一个字节都能够拷贝**
错误代码示例1:
void* my_memcpy(void* dest,const void* src, size_t sz)
{
assert(dest && src);
while (sz--)
{
*(char*)dest = *(char*)src;
(char*)dest++;
(char*)src++;
}
return dest;
}
错误代码示例2:
void* my_memcpy(void* dest,const void* src, size_t sz)
{
assert(dest && src);
while (sz--)
{
*(char*)dest = *(char*)src;
dest = ++(char*)dest ;
src = ++(char*)src ;
}
return dest;
}
错误1具体原因其实我也没有搞懂,错误2只能在某些编译器下可以正常运行,但不是全部,因此就算作错误
memmove(Move block of memory)使用和模拟实现
代码格式:
void * memmove ( void * destination, const void * source, size_t num );
memmove使用要点:
1:和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
2:如果源空间和目标空间出现重叠,就得使用memmove函数处理
代码示例:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
代码解析:
这里只有一个数组arr1,我们传入的地址为arr1+2和arr1,分别表示数组第三个元素的地址(&arr1[3]),和数组首元素的地址
memmove的模拟实现:
在实现之前我们先理解一下思路:
memmove的实现其实有一种比较简单的方法,就是我们只需要创造一个和arr一模一样的数组brr,由于是两个不同的数组,内存地址是不同的,只是数组元素是相同的,这样我们就解决重叠的问题了
但是我们能不能只通过arr自身而不去创造brr去实现memmove呢?
如图:我们需要将绿色方框中的元素拷贝到红色方框中的元素,可以像这样拷贝,我们先将a[4]=3拷贝到a[6]=5中,然后将a[3]=2拷贝到a[5]=4中…这样就可以避免重叠拷贝,造成拷贝元素不是我们想要的情况了
而如果我们需要将图中绿色方框中的元素拷贝到前面红色方框的元素呢?
其实方式都是一样的
综上如果我们只用数组arr而不创造数组brr的话,我们需要考虑到两种情况,一个是往前拷贝,一个是往后拷贝,所以针对情况我们就一定会用到if语句
具体实现代码如下:
void* my_memmove(void* dest, const void* src, size_t sz)
{
assert(dest && src);
void* ret = dest;//记录dest启始地址
if (dest < src)//从前向后
{
for (int i = 0; i < sz; i++)
{
*(char*)dest = *(char*)src;
dest = (char*)+1;
src = (char*)src + 1;
}
}
else//从后向前
{
while (sz--)
{
*((char*)dest + sz) = *((char*)src + sz);
}
}
return ret;
}
代码解析:
* (char * )dest = * (char*)src是将dest和src强制转换为char*,方便后续可以一个字节一个字节的拷贝
dest = (char*)+1和src = (char*)src + 1是将两个数组都强转后向后移动一个字节
由于while(sz–)是先判断sz是否=0,判断完后再执行后置减减,然后在通过 *((char *)dest + sz) = * ((char * )src + sz)确定需要拷贝的数组和要拷贝数字的位置,我们用一个图来说明:
我们需要将绿色方框中的元素拷贝进红色方框中去,下面是各元素的16进制表示
memset(Fill block of memory)函数的使用
代码格式:
void * memset ( void * ptr, int value, size_t num );
memset使用要点:
1:memset是用来设置内存的,将内存中的值以字节为单位设置成想要的内容
代码示例:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world";
memset(str, 'x', 6);
printf("%s",str);
return 0;
}
代码解析:
memset中我们传入的
扩展
memset是以字节为单位设置内存的,为了方便理解我们看一个代码
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 40);
for (int i = 0; i < 10; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
运行结果如图:
我们调试看一下内存
这是以16进制的方式存储的,在还没有执行memset时内存存储的每个字节都是0
而执行memset后我们可以看到每隔一个字节都会有一个0变成1,因此我们通过这个就比较容易理解以字节为单位设置内存这句话了
所以memset最好还是设置char类型的数组
memcmp(Compare two blocks of memory)函数的使用
代码格式:
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
memcmp使用要点:
1:比较从ptr1和ptr2指针指向的位置开始,向后的num个字节
2:memcmp是比较两个内存块
返回值如下
代码示例:
int main()
{
int arr1[] = { 1,2,3,4,5,6,7 };
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00...
int arr2[] = { 1,2,3 };
//01 00 00 00 02 00 00 00 03 00 00 00
int a = memcmp(arr1, arr2, 12);
printf("%d", a);
return 0;
}
代码结果如下:
但如果我们稍微改一下代码呢?
int main()
{
int arr1[] = { 1,2,3,4,5,6,7 };
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00...
int arr2[] = { 1,2,3,0x11223304 };
//01 00 00 00 02 00 00 00 03 00 00 00 04 33 22 11
int a = memcmp(arr1, arr2, 13);
printf("%d", a);
return 0;
}
可以看到我们将arr2中添加了一个16进制的元素0x11223304 ,并且在memcmp中我们将比较的12个字节改为13个字节,结果如下
结果是0,从这里我们也可以看出,memcmp也是将数组中的元素用16进制来表示,再通过一个字节一个字节的比较,方式有点像前面的memset