目录
一.memcpy函数
二.memmove函数
三.memset函数
四.memcmp函数
一.memcpy函数
该函数是针对内存块进行拷贝操作,mem即为memory,是内存的意思;cpy就是copy,是拷贝的意思
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
如上两个数组,我们如何才能不通过循环的方式将arr数组中的前五个元素放入arr2数组呢?
这时就需要用到memcpy函数
void* memcpy(void* destination, const void* source, size_t num)
注意事项:
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的位置
- 这个函数在遇到'\0'时并不会停下来
- 如果source和destination有任何的重叠,复制的结果都是未定义的(源空间和目标空间在内存使用上不能有重叠)
- 使用时需要引用头文件<string.h>
- 学会memcpy函数的模拟实现
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
memcpy(arr2, arr, 20); //就是把20个字节的内容从arr数组中存入到arr2数组中,即5个元素(整型变量一个占4字节)
return 0;
}
模拟实现:
两个指针在开始时,分别指向开始处,可以通过一个字节一个字节拷贝来实现memcpy的功能
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
my_memcpy(arr2, arr, 20); //就是把20个字节的内容从arr数组中存入到arr2数组中,即5个元素(整型变量一个占4字节)
return 0;
}
//memcpy函数拷贝结束后,会返回目标空间的起始地址,但返回以后需不需要创建一个新指针接收返回的起始地址是随意的
void* my_memcpy(void* dest,const void* src, unsigned int num) //把万能指针作为形参是为了解决实参的不同类型(可能是字符型也可能是整型)
{
void* ret = dest;
//一个字节一个字节拷贝,循环num次即可
int i = 0;
assert(dest && src); //两个指针不能为空
while (num--) //后置--,先使用再--
{
*(char*)dest = *(char*)src; //为了一个字节一个字节拷贝,所以可以将两个万能指针强转成char*指针(指向char类型的指针),然后再解引用,访问1字节的数据
src = (char*)src + 1; //用于运算的表达式必须是指向完整对象类型的指针,因此需要再次进行强制转换;但用于接收的表达式可以是万能指针
dest = (char*)dest + 1; //同时不能写成(char*)src++的形式,因为强制转换操作符的优先级与++相同,但这两个操作符是从右往左运算,先使用了src,再++,最后在强转,违背了上一条规律
//但可以写成((char*)src)++这种形式,可以看成(char*)src = (char*)src + 1, = 前面的src强制转化就是在把void * 类型强制转换为char* 类型,依然满足需求
//笔者在此推荐src = (char*)src + 1这种形式,因为这种形式下用于接收的src还是void*类型,什么指针类型都能接收,而char* 类型只能接受char* 类型
//而对于强制类型转换,一个变量的数据类型被强制转换后,出了这条代码后就不会再保持被强制转换后的数据类型了,因为强制类型转换只是作为运算符存在,并不能真的改变src或者dest的数据存放类型
}
return ret;
}
注:模拟实现的代码思想与注意事项在上文代码中的批注里
1.那为什么我们必须一个字节一个字节拷贝呢?
如果是以整型变量的数据大小(4字节)来拷贝,那如果我们传入的num大小为7就无法完成拷贝工作,因此需要以1个字节大小来进行拷贝
2.对于上文中所提到的source和destination有任何的重叠,复制的结果都是未定义的,是什么意思?
例如要让arr的前5个数据(12345)memcpy到第三个数据(3)开始中去,即使memcpy函数可以让arr数组变成1212345(vscode编译器),但是是存在复制结果未定义风险的
二.memmove函数
该函数与memcpy函数的差别就是memmove处理的源内存块和目标内存块可以是重叠的,因此如果源空间和目标空间出现重叠,就得使用memmove函数处理
模拟实现:
在模拟函数时,关键在于如何将一个数组里的重叠元素拷贝,共分为以下两种情况:
1.src(源空间)的首元素在dest(目标空间)的首元素前,这种情况下需要从dest以及src的末尾元素开始进行拷贝
2.src的首元素在dest的首元素后,这种情况下需要从dest以及src的首元素开始进行拷贝
情形1:
情形2:
下图含义为,当dest在src前,就从前往后拷贝;当dest在src后,就从后往前拷贝;当src和dest没有重合,就即可从后往前又可以从前往后,笔者在下面的代码中只会尝试从后往前的情况
void my_memmove(void* dest, void* src, unsigned int num)
{
assert(dest && src);
void* ret = dest;
if (dest < src) //存放地址的顺序前后比较,数组是连续存放,存放在低地址处(下标较小处)的顺序靠前,存放在高地址处(下标较大处)的顺序靠后,顺序越靠前越小,越靠后越大
{
//从前往后
while (num--)
{
*(char*)dest = *(char*)src;
src = (char*)src + 1;
dest = (char*)dest + 1; //从前向后的方式上文已经分析过,此处略
}
}
else
{
//从后往前
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
//从后往前,一开始是在最前面的,加上num就变成最后了,随着num减小,两者也在跟着往低地址处跑
}
}
return ret;
}
三.memset函数
void* ptr:指向要填充的内存块的指针
int value:要设置的值。该值作为 int 传递,但该函数使用此值的无符号字符转换填充内存块
size_t num:要设置为该值的字节数
但请注意,对于整型来说,memset函数是以字节为单位进行存放的
int arr[5] = { 0 };
memset(arr, 1, 20);
int i = 0;
for (i = 0; i <= 4; i++)
{
printf("%d", arr[i]);
}
上述代码最终打印结果为16843009,这是因为memset函数是以字节为单位进行存放,在1个字节当中,10进制的1的16进制表示为:01,如下图所示。一个字节一个字节以01的16进制方式存入内存,而一个整型占四个字节,所以最后输出的结果为16进制下的01 01 01 01,因此才会输出非常大的一个结果
四.memcmp函数
num即为所需比较的字节数量,且memcmp函数是以字节为单位进行比较的(下文有具体讲解)
该函数就是用来完成内存块比较的。返回值分为正、负、0三种情况;正为大,负为小,0为相等(与strncmp函数返回值含义相同,且非常相似,区别就在于一个可以面向所有类型数据,一个只是面向了字符串与字符型数据)
memcmp函数是以字节为单位进行比较的,下面笔者进行讲解:
int arr1[] = { 1,2,3,4,5,6,7,8 };
int arr2[] = { 1,2,3,4,8,8,8,8 };
int ret = memcmp(arr1, arr2, 17);
printf("%d", ret);
在vscode2022编译器下,最终结果为-1。
因为在vscode编译器里是以小端存储(相关文章链接:数据存储),因此小的数字放在高位;而比较17个字节最终的比较是 05和08 这两个以16进制表示的一字节,自然而然就有arr2的数据要比arr1大的结果