C语言内存函数
memcpy
之前我们学习了字符串拷贝函数strcpy,今天我们学习内存拷贝函数memcpy,它的头文件也是string.h
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置
- 这个函数在遇到 ‘\0’ 的时候并不会停下来,它只依靠num停下
- 如果按照C标准来说,若是source和destination有任何部分重叠,复制都会出错;但实际上对于笔者所用的这版VS,即使内容有重叠也可以正常使用(但我可没鼓励过你们有重叠也用memcpy)
memcpy的使用方法及模拟实现都与strcpy极其相似,实际上,完全可以用memcpy代替strcpy;
代码如下:
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* destination, const void* source, size_t num)
{
assert(destination && source);
void* ret = destination;
while (num--)
{
*((char*)destination) = *((char*)source);
(char*)destination = (char*)destination + 1;
//还有种不推荐的写法:((char*)source)++;用括号强制改变操作符结合性
(char*)source = (char*)source + 1;
}
return ret;
}
int main()
{
int i = 0;
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 5 * sizeof(int));
for (i = 0; i < 5; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
注意这段代码:
while (num--)
{
*((char*)destination) = *((char*)source);
(char*)destination = (char*)destination + 1;
//还有种不推荐的写法:((char*)source)++;用括号强制改变操作符结合性
(char*)source = (char*)source + 1;
}
这段代码的运行顺序是(为方便表述,这里定义num初始值为3):
- 先使用,num==3;为真;减减变成2;执行循环内容
- 先使用,num==2;为真;减减变成1;执行循环内容
- 先使用,num==1;为真;减减变成0;执行循环内容
- 先使用,num==0;为假;减减变成-1;跳出循环
memmove
它的头文件也是string.h
void * memmove ( void * destination, const void * source, size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和⽬标内存块是可以重叠的
- 如果源空间和⽬标空间出现重叠,就得使⽤memmove函数处理,当然不重叠也可以用
为什么刚才我们写的my_memcpy不能用于对重叠内存块进行拷贝呢?就拿下面代码为例:
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* destination, const void* source, size_t num)
{
assert(destination && source);
void* ret = destination;
while (num--)
{
*((char*)destination) = *((char*)source);
(char*)destination = (char*)destination + 1;
//还有种不推荐的写法:((char*)source)++;用括号强制改变操作符结合性
(char*)source = (char*)source + 1;
}
return ret;
}
int main()
{
int i = 0;
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int arr2[10] = { 0 };
my_memcpy(arr1 + 2, arr1, 5 * sizeof(int));
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
调试控制台结果:
注:以下的每次改变是以int为单位的,箭头的名字只是用于分辨哪个是源哪个是目标而已,不是说指向的位置就是arr1和arr1 + 2
刚开始:
第一次改变:
第二次改变:
第三次改变:
第四次改变:
第五次改变:
可以看到,在拷贝的过程中,源的内容被破坏了,因此拷贝出错
知道了错在哪,再写my_memmove就简单一些了,只要让源先拷贝过去在改变就行了,就比如刚刚的这五次改变,从前往后拷贝会出问题,那从后往前拷贝呢?
这样的话:
初始位:
第一次:
第二次:
第三次:
第四次:
第五次:
但从后向前拷贝适用于一切重叠情况吗?
当然不是,比如现在把实参改成:
my_memcpy(arr1, arr1 + 2, 5 * sizeof(int));
那从后往前拷贝反而会出问题,而从前向后拷贝则正常
所以花半天图得出的最后结论是要写个分支结构,重叠情况不同,拷贝方法也不同
可以分为三种情况:
- 不重叠:从前往后和从后往前都可以,那个方便用哪个
- 重叠1:从前往后拷贝
- 重叠2:从后往前拷贝
接下来就是要找到这两种重叠的边界条件了:
我们很容易得到:当dest处于这个区间(sour < dest < (char*)sour + num)是要从后往前拷贝的:
当(char*)dest + num处于这个区间(sour < (char*)dest + num < (char*)sour + num)的时候是从前往后拷贝的:
memmove的实现:
注意:千万不要把我们刚刚总结出来的区间直接往if()里填,我第一次就是直接往里填,出错才想到C语言里没有区间,比如a<b<c是先判断a<b是真是假,再拿这个真或者假去和c比较
#include<stdio.h>
#include<string.h>
#include<assert.h>
//第一版:sour < dest < (char*)sour + num
void* my_memmove_1(void* destination, const void* source, size_t num)
{
assert(destination && source);
void* ret = destination;
if (source < destination && destination < (char*)source + num)
{
while (num--)
{
*((char*)destination + num) = *((char*)source + num);
}
}
else
{
while (num--)
{
*((char*)destination) = *((char*)source);
(char*)destination = (char*)destination + 1;
(char*)source = (char*)source + 1;
}
}
return ret;
}
//第二版:sour < (char*)dest + num < (char*)sour + num
void* my_memmove_2(void* destination, const void* source, size_t num)
{
assert(destination && source);
void* ret = destination;
if (source < (char*)destination + num && (char*)destination + num < (char*)source + num)
{
while (num--)
{
*((char*)destination) = *((char*)source);
(char*)destination = (char*)destination + 1;
(char*)source = (char*)source + 1;
}
}
else
{
while (num--)
{
*((char*)destination + num) = *((char*)source + num);
}
}
return ret;
}
int main()
{
int i = 0;
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int arr2[10] = { 0 };
//memmove(arr1 + 2, arr1, 5 * sizeof(int)): 1 2 1 2 3 4 5 8 9 10
//memmove(arr1, arr1 + 2, 5 * sizeof(int)): 3 4 5 6 7 6 7 8 9 10
my_memmove_2(arr1, arr1 + 2, 5 * sizeof(int));
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
memset
它的头文件也是string.h
void * memset ( void * ptr, int value, size_t num );
memset是⽤来设置内存的,将内存中的值以字节为单位设置成想要的内容。
我曾经疑惑第二个形参为什么是int,后来问了AI,得到的结果是:
这个函数没什么好说的,就是以字节为单位批量赋值
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = "hello word";
memset(str, 'x', 7);
printf("%s\n", str);
return 0;
}
调试控制台结果:
但也是因为是以字节为单位批量赋值,所以如果对一个int数组赋值,要稍微注意一下
比如将一个int数组全初始化为1:
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
memset(arr, 1, 10 * sizeof(int));
for (; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这样写肯定会出问题,调试控制台结果:
因为它是字节为单位,而非元素为单位:
memcmp
它的头文件还是string.h
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
- ⽐较从ptr1和ptr2指针指向的位置开始,向后的num个字节
- 返回值如下:
直接上代码:
#include<stdio.h>
#include<string.h>
int main()
{
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, 5 * sizeof(int));
printf("%d\n", ret);
return 0;
}
调试控制台结果:
我们老师说比较顺序是:01比01,00比00,00比00,00比00……而不是00比00,00比00,00比00,01比01
为什么呢?似乎是我还没学到的知识,在此先埋个坑,应该会在下篇博客得到解决。
完