> 文章列表 > 详解C语言string.h中常用的14个库函数(四)

详解C语言string.h中常用的14个库函数(四)

详解C语言string.h中常用的14个库函数(四)

本篇博客会讲解最后4个函数,分别是memset, memcpy, memmove, memcmp。这4个函数开头都是mem,即memory(内存)的缩写。

详解C语言string.h中常用的14个库函数(四)

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));

打开内存窗口观察一下,这是设置前:

详解C语言string.h中常用的14个库函数(四)

这是设置后:
详解C语言string.h中常用的14个库函数(四)
可以看到,成功把前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中的数据打印出来,就可以看到拷贝成功了。
详解C语言string.h中常用的14个库函数(四)
这个函数和接下来会讲解的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数组打印出来,结果如下:
详解C语言string.h中常用的14个库函数(四)
可以看到并不符合预期。原因是,由于源头和目的地有重叠,在拷贝左边的数据时,其实已经把源头给覆盖了。

事实上,如果使用库里的memcpy来拷贝,源头和目的地有重叠时,结果是标准未定义的。此时不能使用memcpy,而应使用memmove。

memmove

oid * memmove ( void * destination, const void * source, size_t num );

memmove和memcpy的使用方式完全相同。唯一的区别是,使用memmove来拷贝时,源头和目的地是可以重叠的。

这里我们来模拟实现一下这个函数。假设源头和目的地没有重叠,前面memcpy的实现是没有任何问题的,但是一旦有重叠时,需要分类讨论。

  1. src<dst,此时源头在目的地的左边,应该先拷贝右边的数据,再拷贝左边的数据。
  2. 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打印出来,如下:
详解C语言string.h中常用的14个库函数(四)
而如果是这样:

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr, arr + 2, 5 * sizeof(int));

数组arr打印出来的结果如下:
详解C语言string.h中常用的14个库函数(四)

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),结果将是“等于”。

总结

  1. 本篇博客主要讲解了4个内存操作函数。
  2. memset是用来设置内存中的值的。
  3. memcpy和memmove都可以用来拷贝内存中的值,如果源头和目的地空间有重叠,则必须用memmove。
  4. memcmp是用来比较内存中的值的大小的,返回值和strcmp类似,但是memcmp是比较出大小或者比较完num个字节就结束,而strcmp是比较出大小或者比较到\\0就结束。

感谢大家的阅读!