本篇博客会讲解最后4个函数,分别是memset, memcpy, memmove, memcmp。这4个函数开头都是mem,即memory(内存)的缩写。
memset
void * memset ( void * ptr, int value, size_t num );
memset可以用来设置内存中的值。该函数可以把从ptr指向的空间开始,后面的num个字节设置成value的值。
举个简单的例子。假设有一个数组:
int arr[] = { 1,2,3,4,5 };
我们想把这个数组的前3个数都设置成0,就这么写:
memset(arr, 0, 3 * sizeof(int));
打开内存窗口观察一下,这是设置前:
这是设置后:
可以看到,成功把前3个数设置成了0。
memcpy
void * memcpy ( void * destination, const void * source, size_t num );
memcpy是用来拷贝内存中的数据的。拷贝的起始位置由第2个参数决定,目标位置由第1个参数决定,注意:目标在前,源头在后,后面的memmove同理。总共拷贝多少个字节的数据,由第3个参数决定。函数会返回目标空间的起始地址。
比如,假设有2个数组:
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 0 };
如果我想把arr1的数据都拷贝到arr2中,就应该这么写:
memcpy(arr2, arr1, sizeof(arr1));
拷贝完后,把arr2中的数据打印出来,就可以看到拷贝成功了。
这个函数和接下来会讲解的memmove函数都是非常重要的,所以我模拟实现一下。模拟实现的思路很简单,每次从源头拷贝到目的地,拷贝num次即可。
void* my_memcpy(void* dst, const void* src, size_t num)
{
assert(dst && src);
void* ret = dst;
while (num--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;
}
根据以上的实现,我们可以发现,使用memcpy进行内存拷贝时,源头和目的地的空间是不能重叠的。加入重叠了,比如:
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr + 2, arr, 5 * sizeof(int));
以上程序中,数组arr的内存分布是(左边是低地址):[1 2 3 4 5 6 7 8 9 10]。我们把[1 2 3 4 5]拷贝到了[3 4 5 6 7]所在的位置。预期的结果是:[1 2 1 2 3 4 5 6 7 8 9 10]。假设把拷贝后的arr数组打印出来,结果如下:
可以看到并不符合预期。原因是,由于源头和目的地有重叠,在拷贝左边的数据时,其实已经把源头给覆盖了。
事实上,如果使用库里的memcpy来拷贝,源头和目的地有重叠时,结果是标准未定义的。此时不能使用memcpy,而应使用memmove。
memmove
oid * memmove ( void * destination, const void * source, size_t num );
memmove和memcpy的使用方式完全相同。唯一的区别是,使用memmove来拷贝时,源头和目的地是可以重叠的。
这里我们来模拟实现一下这个函数。假设源头和目的地没有重叠,前面memcpy的实现是没有任何问题的,但是一旦有重叠时,需要分类讨论。
- src<dst,此时源头在目的地的左边,应该先拷贝右边的数据,再拷贝左边的数据。
- dst<src,此时源头在目的地的右边,应该先拷贝左边的数据,再拷贝右边的数据。
以第1点为例,第2点同理。假设有一个数组,内存分布(左边是低地址)是:[1 2 3 4 5 6 7 8 9 10]。假设想把[1 2 3 4 5]所在位置的数据拷贝到[3 4 5 6 7]所在的位置,假设先拷贝左边的数据,再拷贝右边的数据。一上来先把1拷贝到3所在的位置,得:[1 2 1 4 5 6 7 8 9 10],再拷贝2:[1 2 1 2 5 6 7 8 9 10],此时我们应该拷贝3,但是3呢?不见了,已经被覆盖了。所以在这种情况下,先拷贝左边的数据是不对的,应该先拷贝右边的数据。
分类讨论后,实现如下:
void* my_memmove(void* dst, const void* src, size_t num)
{
assert(dst && src);
void* ret = dst;
if (dst < src)
{
// 左->右
while (num--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
}
else
{
// 右->左
while (num--)
{
*((char*)dst + num) = *((char*)src + num);
}
}
return ret;
}
此时再运行:
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 5 * sizeof(int));
把arr打印出来,如下:
而如果是这样:
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr, arr + 2, 5 * sizeof(int));
数组arr打印出来的结果如下:
memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
memcmp是用来比较内存中的值的,它会一个字节一个字节向后比较,直到遇到某一对数据比较出大小,或者所有num个字节的数据都相等。
返回值和strcmp类似,如果左边>右边返回正数,左边<右边返回负数,左边==右边返回0。
比如,在小端机器下,运行以下程序:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 1,2,3,4,6 };
int ret = memcmp(arr1, arr2, 4 * sizeof(int) + 1);
if (ret > 0)
{
printf(">\n");
}
else if (ret < 0)
{
printf("<\n");
}
else
{
printf("==\n");
}
return 0;
}
得到的结果是“小于”。因为arr1的内存分布是(左边是低地址,字节和字节之间用空格分隔):[01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00],arr2的内存分布是:[01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 06 00 00 00],一直比较,直到05<06,得到“小于”。如果把memcmp的第3个参数改成4*sizeof(int),结果将是“等于”。
总结
- 本篇博客主要讲解了4个内存操作函数。
- memset是用来设置内存中的值的。
- memcpy和memmove都可以用来拷贝内存中的值,如果源头和目的地空间有重叠,则必须用memmove。
- memcmp是用来比较内存中的值的大小的,返回值和strcmp类似,但是memcmp是比较出大小或者比较完num个字节就结束,而strcmp是比较出大小或者比较到
\0
就结束。
感谢大家的阅读!