3. 内存操作函数
3.1 memcpy
void * memcpy ( void * destination, const void * source, size_t num );
对指定单位数据进行拷贝,处理不重叠的拷贝。
函数细节
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到’\0’ 的时候并不会停下来,只有当元素拷贝完成后才会停下来。
memcpy需要拷贝所有的类型的数据,所以用void*指针。
count单位为字节。
使用方法
int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[5] = { 0 };//把arr1的前五个拷贝到arr2中 //把前五个数据的内存拷贝到arr2中 //memcpy memcpy(arr2, arr1, 20);//20个字节,五个整形 int i = 0; for (i = 0; i < 5; i++) { printf("%d ", arr2[i]); } return 0; }
运行结果:
模拟实现
图:
void* my_memcpy(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest;//拷贝一份地址 while (count--) { *(char*)dest = *(char*)src;//void类型指针使用时需要强制类型转换 //plan1 /*dest = (char*)dest + 1; src = (char*)src + 1;*/ //plan2 ((char*)dest)++; //(char*)dest++;//err //后置++针对的是dest,因为++优先级比较高, //但是void*是不能++的 /**所以在没有计算之前,它就已经出现了错误,没有机会类型转换 *但如果在(char*)dest加上一个括号,形成聚组操作符 *让它先强制类型转换,那么就没什么问题了*/ ((char*)src)++; //plan3 /* ++(char*)dest; ++(char*)src; */ //而前置++则可以,因为操作符的结合性只在数据和两个操作符相接的时候 //才会涉及到结合性,当前情况并不涉及结合性,所以我们的数据肯定是先和char*结合 //先被强制类型转换了,再进行++,现在针对的就是强转后的类型 } return ret;//返回void*指针 } int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[5] = { 0 }; my_memcpy(arr1, arr2, 20); for (int i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; }
运行结果:
函数缺陷
那么我们实现的memcpy是否可以处理重叠的拷贝(同一块内存空间中的数据拷贝)?
例如将一个数组从1开始的五个元素拷贝到第三个元素往后五个元素的位置:
测试:
void* my_memcpy(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest; while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } return ret; } int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; my_memcpy(arr1 + 2, arr1, 20); for (int i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; }
运行结果:
分析;
我们发现这是完全不行的,因为在同一块内存中,它们使用的是相同的空间,在对内存进行修改时,也会把原本的数据修改,当下标为2的位置的数据被改变后,当拷贝第三个元素时,原先的数据3已经被修改成了1,所以内存空间在拷贝时,数据全为1/2,无法完成拷贝。
那么如何完成对相同内存的拷贝?别急,马上就为您解答!
3.2 memmove
void * memmove ( void * destination, const void * source, size_t num );
对指定单位数据进行拷贝,重叠和不重叠的都能搞定。
函数细节
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
使用方法
int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9, 10 }; memmove(arr1 + 2, arr1, 20);//可以实现重叠内存的拷贝 int i = 0; int sz = sizeof(arr1) / sizeof(arr1[0]); for (i = 0; i < sz; i++) { printf("%d ", arr1[i]); } return 0; }
运行结果:
模拟实现
memmove可以从前往后拷贝,也可以从后向前拷贝.
想象一下,当dest < src时,如果从后向前拷贝,是否就把重叠部分原先要拷贝的元素覆盖掉了?比如要将34567拷贝到12345的位置,先将7拷贝,是否将源位置的5给覆盖了?所以当dest < src时要从前往后拷贝。
当dest > srtc && dest < src + count时,如果从前往后拷贝,比如要将34567拷贝到原先4开始向后五个元素处,先拷贝3就把要拷贝的4给覆盖了,这样就错误了。所以当dest > src && dest < src + count时,需要从后往前拷贝。
而当dest > src + count时,则随便从前往后或从后往前,因为目标位置和源位置不可能会有重叠处,随便拷贝。
图:
而关于memmove又有两种实现方式:
1.左边采用从前向后拷贝的方式,中间和右边采用从后向前拷贝的方式。
这个方法比较简单,只需要给出dest < src的情况即可。
2.左边和右边采用从前向后拷贝的方式,中间采用从后向前拷贝的方式。
这个方法复杂些,区别中间的区间即可,这时dest > src 并且 dest需要小于src+字节数。
void* my_memmove(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest; //1 //左边采用从前向后拷贝的方式,中间和右边采用从后向前拷贝的方式 if (dest < src) { //从前向后 while (count--) { *((char*)dest) = *((char*)src); dest = (char*)dest + 1; src = (char*)src + 1; } } else { //从后向前 while (count--) { *((char*)dest + count) = *((char*)src + count); //从最后元素的最后一个字节开始向前拷贝,count会不断调整 } } //2 //左边和右间采用从前向后拷贝的方式,中间采用从后向前拷贝的方式 //if (dest > src && dest < ((char*)src + count)) //{ // //从后向前 // while (count--) // { // *((char*)dest + count) = *((char*)src + count); // } //} //else //{ // //从前向后 // while (count--) // { // *((char*)dest) = *((char*)src); // dest = (char*)dest + 1; // src = (char*)src + 1; // } //} return ret; } int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9, 10 }; //从前往后拷贝或者从后向前拷贝 my_memmove(arr1 + 2, arr1, 20); int i = 0; int sz = sizeof(arr1) / sizeof(arr1[0]); for (i = 0; i < sz; i++) { printf("%d ", arr1[i]); } return 0; }
运行结果:
memcpy的意义
可能到这里大家可能会有点疑惑,既然memmove既包含了memcpy的功能,甚至还能处理memcpy无法处理的情况,那么memcpy这个函数还有存在的意义吗?
实际上这只是片面的看法,虽然memmove比memcpy更加好用,但是memcpy更加重要,也许memcpy比memmove先出现,之前别人都是使用的memcpy;
而且语言是迭代更替的,是一代一代发展的,很多使用旧版本习惯的人可能习惯于使用memcpy,而且比如一个产品之前就是使用的memcpy,如果出现了memmove还要将版本全部重改一遍吗,如果盲目删除这个语法,就会带来很大的问题。
并且实际上vs中其实把memcpy进行了优化,我们模拟实现的memcpy不能实现相同内存的拷贝,但是使用库里的memcpy可以实现:
3.3 memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
对指定单位内容进行比较
函数细节
比较从ptr1和ptr2指针开始的num个字节
第一组数据的一个字节内容若小于第二组数据的一个字节内容返回小于0的值
第一组数据的一个字节内容若等于第二组数据的一个字节内容返回0
第一组数据的一个字节内容若大于第二组数据的一个字节内容返回大于0的值
使用方法
int main() { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 1,2,3,4,0x11223305 }; int ret = memcmp(arr1, arr2, 17); printf("%d\n", ret); ret = memcmp(arr1, arr2, 20); printf("%d\n", ret); return 0; }
运行结果:
分析:
5翻译成16进制位0x00000005,小端存储为05 00 00 00,和0x11223305小端存储时,前17个字节相同,从第18个字节开始不相同。
模拟实现
int my_memcmp(const void* arr1, const void* arr2, size_t count) { assert(arr1 && arr2); while (count--) { if (*(char*)arr1 != *(char*)arr2) { return (*(char*)arr1) - (*(char*)arr2); } arr1 = (char*)arr1 + 1; arr2 = (char*)arr2 + 1; } return 0; } int main() { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 1,2,3,4,0x11223305 }; int ret = my_memcmp(arr1, arr2, 17); printf("%d\n", ret); return 0; }
运行结果:
3.4 memset
void *_memccpy( void *dest, const void *src, int c, unsigned int count );
对指定单位进行内存设置,设置的元素相同
函数细节
- 内存设置的元素相同
- 以字节为单位来初始化内存单元
- 接收数据的最大值为ff,也就是一个字节的最大数据
使用方法
int main() { int a[] = { 1,2,3,4,5 }; memset(arr, 1, 20); int sz = sizeof(arr)/sizeof(arr[0]); for(int i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
分析:
memset操作的是字节,将20个字节单位改成1,那么设置的元素就不是1,而是16进制位0x01010101的元素。
调试结果:
运行结果:
模拟实现
void* my_memset(void* dest, int c, size_t count) { assert(dest); void* ret = dest; while (count--) { *(char*)dest = c; dest = (char*)dest + 1; } return ret; } int main() { int arr[] = { 1,2,3,4,5 }; my_memset(arr, 1, 20); return 0; }
运行结果:
4. 结语
到此本篇博客到此结束!本篇博客对常见的字符串和内存函数作出了一定归纳,相信通过这篇文章,大家也对于这些函数也有了一定的理解,当然可能也有一些不到位的地方,如有错误,还请指正!













