文章目录
前言
C语言内存函数包括memcpy, memmove, memset, memcmp等,可以用于数据的复制、赋值、比较等;其操作以字节为单位,不关心数据的具体类型。
内存函数都包含在#include <string.h
这个头文件下,在使用的时候要包含头文件。
我们先来看memcpy,和strcpy原理类似,
1. memcpy使用和模拟实现
1.1 memcpy的使用
如官网所示,strcpy将num字节的值从源指向的位置直接复制到目标指向的内存块。
使用时注意以下几点:
- 源指针和目标指针指向的对象的底层类型与此函数无关;结果是数据的二进制副本。
- 该函数不检查源代码中的任何终止空字符,它总是精确地复制num字节。
- 为了避免溢出,目标和源参数指向的数组大小应至少为num字节,并且不应重叠(对于重叠的内存块,memmove是一种更安全的方法。
如果src指向的数组长度小于num字节,如下图所示,对于未定义的内存数据,系统会拷贝所指向内存的脏数据。
值得一提的是,对于dest与src所指向内存重叠的话,推荐使用memmove函数进行值的拷贝,也就是说,memcpy是不保证在这种情况下结果的准确性的。然而VS这个满分选手,在dest与src重叠时也能够很好的实现值的拷贝,如下图所示。
1.2 memcpy的模拟实现
接下来我们思考如何进行memcpy函数的实现,当然我们只满足基本需求,暂时不考虑内存块重叠的情况。
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num) {//参数设置与C语言官网一致
assert(dest && src);//在使用指针之前要进行判空
void* ret = dest;//将目的地指针进行存储,方便后续进行返回
for (size_t i = 0; i < num; i++)//i的类型和num保持一致
{
*((char*)dest) = *((char*)src);
dest = (char*)dest + 1;
//注意:此处不能用dest++或++dest,因为dest是泛型指针,无法进行运算;
//也不能用(char*)dest++或++(char*)dest,因为强制类型转换是暂时的,没有转换dest的类型,
//只是将强转结果用于操作,在++的时候已经是下一步操作了,强转失效;
//且C语言不支持(char*)dest++,但VS2022的.c文件支持++(char*)dest,.cpp文件两个都不支持
src = (char*)src + 1;
//上面三行代码也可优化为 *((char*)dest+i) = *((char*)src+i);
//或者 *((char*)dest+num-i) = *((char*)src+num-i);但没必要
}
//也可使用while循环
/*while (num--) //num-- 后置--,先使用,后--
{
*((char*)dest) = *((char*)src);
dest = (char*)dest + 1;
src = (char*)src + 1;
//上面三行代码也可直接写成*((char*)dest+num) = *((char*)src+num);
}*/
return ret;
}
int main()
{
int dest[5] = { 0 };
int src[10] = { 1,3,9,18,36 };
memcpy(dest, src, 17);
for (int i = 0; i < 5; i++)
printf("%d ", dest[i]);
//下面是char类型数组的测试
/*char arr1[] = { 'a','b','c','d' };
char arr2[11] = "A";
memcpy(arr2 + 1, arr1, 4);
printf("%s\n", arr2);*/
return 0;
}
VS2022 x64运行结果如下图所示
1 3 9 18 36
2. memmove使用和模拟实现
其实memmove与memcpy区别不大,就在于memmove对于目标地址和原地址重叠的情况也能很好的作用,而重叠的情况官网对memcpy不做要求。
2.1 memmove使用
- 将num字节的值从source指向的位置复制到destination指向的内存块。复制就像使用中间缓冲区一样进行,允许目标和源重叠。
- 源指针和目标指针指向的对象的底层类型与此函数无关;结果是数据的二进制副本。
- 该函数不检查源代码中的任何终止空字符,它总是精确地复制num字节。为避免溢出,目标和源参数指向的数组大小应至少为num字节。
- 返回指针destination。
2.2 memmove模拟实现
下面我们推演几种可能出现的情况,如下图,当目标指针(绿色)在源指针(橙色)之前,所要复制的源域与目标域部分重叠,如果从最后一个元素开始挨个往前赋值,也就是8变为11,7变为10,6变为9,我们期望的是原来(char*)src+3及之后的4个字节对应的元素8被赋给5的位置,但在复赋值之前,8已经变成11了,
此时赋值,11被赋给5的位置,以及原本应该被赋给3、4的6、7也已经被改变了。但是,当从前往后赋值,不会出现这种情况,因此,当dest1在src之前,从前往后赋值;
同理,dest2在src后,应该从后往前对dest2进行赋值,;而当要复制的原区域与目标完全重叠的情况,此时无所谓前后顺序,无论是从前往后还是从后往前均可。
为了方便,我们算法思想为,当dest<src,从前往后赋值,当dest>=src,从后往前赋值;
代码如下图所示
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if((char*)dest<(char*)src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
*((char*)dest+num) = *((char*)src+num);
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8 };
my_memcpy(arr1, arr1 + 2, 20);
for(int i=0;i<8;i++)
printf("%d ",arr1[i]);
}
VS2022x64运行结果如下图所示
3 4 5 6 7 6 7 8
3. memset函数的使用
其实memset函数挺简单的,void * memset ( void * ptr, int value, size_t num );
以字节为单位将传入指针及其之后共num个字节的内存都赋值为value,并且返回传入的指针;value接受的数值转化为char类型的数据进行赋值,如果value大于一个字节,只取一个字节。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
memset(arr1, 'xy', 5);
printf("%s\n", arr1);
}
VS2022x64运行结果如下图所示
yyyyyf
一定要当心,memset是以字节为单位进行赋值的,如下代码将
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 0 };
memset(arr1, 1, 4);
for(int i=0;i<10;i++)
printf("%d\n", arr1[i]);
}
VS2022x64运行结果如下图所示
16843009 0 0 0 0 0 0 0 0 0
我们打开调试的监视-内存窗口,可以看到arr1存储的数据是0x 01 01 01 01,也就是每个字节都被设为了1.
4. memcmp函数的使用
memcmp也很简单,以字节为单位比较两个传入指针ptr1与ptr2指向的数据,共比较num个字节;如果在比较过程中出现ptr指向值小于ptr2,返回大于零的数字,小于则返回小于0的数字,如果都相当,返回0.
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 1,2 };
int arr2[10] = { 1 };
printf("%d\n", memcmp(arr1, arr2,5));
printf("%d\n", memcmp(arr1, arr2, 4));
printf("%d\n", memcmp(arr1+2, arr2, 4));
}
VS2022x64运行结果如下图所示
1
0
-1
总结
其实官网还有很多内存函数,大家有兴趣可以自行查阅。
内存函数注意,都是以字节为单位进行操作的,今天的内容比较简单,下次见啦!