【C语言学习】C语言内存函数
内存函数的种类共有4种:memcpy、memmove、memset、memcmp。函数的功能和实现与字符字符串函数大致相同,但是内存函数的操作对象更加丰富。
牢记:所有的内存函数不关心内存中存储的内容!
1. memcpy函数

memcpy函数从source的位置开始向后复制num个字节的数据到destination指向的内存位置。
这个函数在遇到 ‘\0’ 的时候并不会停下来。
将一个数组拷贝到另外一个数组,如果源地址数组比num小,memcpy会越界访问,直到找到num个字节数据。
如果source和destination有任何的重叠,复制的结果都是未定义的。
1.1 memcpy函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
//拷贝前5个整数,一个整数是4个字节,一共20个字节
memcpy(arr1, arr2, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
运行结果:
VS 2013调试:
1.2 memcpy函数的模拟实现
思路分析:
核心思想:一个字节一个字节进行拷贝,并返回目标空间的起始地址。
代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
//memcpy函数模拟实现
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
int i = 0;
while (num)
{
//拷贝1个字节
*(char*)dest = *(char*)src;//转化是临时的
dest = (char*)dest + 1;
src = (char*)src + 1;
num--;
}
return ret;
}
//不建议这样写
//my_memcpy(void* dest, const void* src, size_t num)
//{
// void* ret = dest;
// assert(dest && src);
// int i = 0;
// while (num)
// {
// //拷贝1个字节
// *(char*)dest = *(char*)src;//转化是临时的
// ++(char*)dest;
// ++(char*)src;//一些编译器会报错
// num--;
// }
// return ret;
//}
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
//拷贝前5个整数,一个整数是4个字节,一共20个字节
my_memcpy(arr1, arr2, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
运行结果:
当dest和src指向的空间存在重叠:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
int i = 0;
while (num)
{
//拷贝1个字节
*(char*)dest = *(char*)src;//转化是临时的
dest = (char*)dest + 1;
src = (char*)src + 1;
num--;
}
return ret;
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
my_memcpy(arr + 2, arr, 20);//把1 2 3 4 5拷贝到3 4 5 6 7 上去
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果:

我们期望输出的结果是:1 2 1 2 3 4 5 8 9 10
原因:当我们把1拷贝到3的位置,2拷贝到4的位置后,当我们再想把3拷贝到5的位置时,很抱歉,3的位置已经被覆盖为1了,所以拷贝过来就变成1了,同理,4的位置已经被覆盖成2了,拷贝过来结果就变成2。
如果内存出现重叠(源头的空间和目标的空间存在重叠),就会出现这种问题。
是不是我们写的代码水平较差?

我们写的函数不能实现,但是库函数memcpy却能够实现上面的功能。虽然memcpy能够实现功能,但是我们不要使用,因为这是未定义的(前面提过)。
如果我们想要达到我们预期的效果,又该如何来实现呢?
memcpy函数可以处理不重叠的内存之间的数据拷贝,如果内存之间有重叠,就要用到另外一个函数——memmove函数。
2.memmove函数

memmove函数参数类型、返回值同memcpy函数一致。
2.1 memmove函数的使用
当我们把memcpy换成memmove以后,结果就变得和我们预期的目标一样了。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
memmove(arr + 2, arr, 20);//把1 2 3 4 5拷贝到3 4 5 6 7 上去
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果:

使用memcpy函数从前往后拷贝会覆盖后面的值,那从后往前拷贝又行不行呢?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
return ret;
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//1 2 1 2 3 4 5 8 9 10
memcpy(arr + 2, arr, 20);//把1 2 3 4 5拷贝到3 4 5 6 7 上去
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果:

结果同我们使用memmove函数得到的结果如出一辙!这是由于我们从后往前拷贝,不会覆盖掉前面要拷贝的值。
那如果我们从后往前拷贝呢,把3 4 5 6 7拷贝到1 2 3 4 5的位置上,通过上面这种方式来拷贝,还能行吗?
显然是不可行的,如果我们还是按照从后往前这样的方式来拷贝,后面的数值依然会覆盖掉前面的值,又会出现前面的问题。而要把3 4 5 6 7拷贝到1 2 3 4 5的位置上,则需要我们从前向后拷贝。
注意:虽然说VS编译器中标准库的memcpy也能实现重叠拷贝,但是并不代表所有的编译器实现的标准库中的memcpy函数都能实现重叠拷贝!
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//3 4 5 6 7 6 7 8 9 10
memcpy(arr, arr + 2, 20);//把3 4 5 6 7拷贝到1 2 3 4 5上去
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果:

2.2 memmove函数的模拟实现
2.2.1 思路分析
通过上面图解。我们可以总结出下面两条结论:
1、dest < src时,采用从前向后拷贝;
2、dest => src时,采用从后向前拷贝。
2.2.2 代码实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
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);
}
}
return ret;
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//int sz = sizeof(arr) / sizeof(arr[0]);
my_memmove(arr + 2, arr, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果:

3. memset函数

memset函数有三个参数,分别是ptr、value、num。
它的功能是将ptr指向的前num个字节的内容设置为value值,并返回原地址ptr。
值得注意的是,memset是将num个字节每个字节都设置为value值,并不意味着最后的结果就是value。
3.1 memset函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
memset(arr, 1, 20);
return 0;
}
我们将代码调试起来:
我们发现确实改成了1,但是他把每个字节都改成了1,得到的结果还是1吗?
我们把结果打印出来。
由此可见,你想通过memset函数让每个字节都变成1,它是可以做到的,但是如果你想让它让每个整型都变成1,它是做不到的!
但是如果改成0,它是可以做到的,因为每个字节改成0,合起来还是0!
4. memcmp函数

函数是对ptr1指向的前num个字节和ptr2指向的前num个字节的比较,如果发现两个字节内容不匹配的时候,就比较出大小来了。
memcmp函数的返回值依然有三种:
1、<0,说明ptr1指向的字节内容小于ptr2指向的字节内容;
2、=0,说明ptr1指向的字节内容等于ptr2指向的字节内容;
3、>0,说明ptr1指向的字节内容大于ptr2指向的字节内容。
4.1 memcmp函数的使用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abqwertyuio";
int ret = memcmp(arr1, arr2, 5);
printf("%d\n", ret);
}
运行结果:

此外,memcmp除了被用来比较字符和字符串,还可以被用来比较其他类型的数据。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[5] = { 1, 2, 3, 4, 5 };
int arr2[5] = { 1, 3, 5, 7, 9 };
int ret = memcmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}
运行结果:

当我们比较第5个字节的时候,结果就不一样了。

由于第5个字节02小于03,所以结果返回-1。
我们把2换成0x001234503,结果返回0:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[5] = { 1, 0x001234503, 3, 4, 5 };
int arr2[5] = { 1, 3, 5, 7, 9 };
int ret = memcmp(arr1, arr2, 5);
printf("%d\n", ret);
return 0;
}
运行结果:
虽然0x001234503和3看起来八竿子打不着,但是它们存放在内存中第5个字节的内容是一样的,都是03。
更多推荐



所有评论(0)