系统性学习C语言-第十八讲-C语言内存函数
1. memcpy 使用和模拟实现
void * memcpy ( void * destination, const void * source, size_t num );
memcpy
函数,又称内存拷贝函数,将源数组中的内容拷贝到目标数组中,无论数组的类型,均能进行拷贝操作。
num
为要拷贝的字节数。
下面是使用 memcpy
函数的一些注意事项:
函数
memcpy
从source
的位置开始向后复制num
个字节的数据到destination
指向的内存位置。这个函数在遇到
'\0'
的时候并不会停下来。如果
source
和destination
有任何的重叠,复制的结果都是未定义的。
函数使用举例:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
对于重叠的内存,交给 memmove
函数来处理。
memcpy
函数的模拟实现:
void * memcpy ( void * dst, const void * src, size_t count)
{
void * ret = dst;
assert(dst);
assert(src);
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
return(ret);
}
在对 memcpy
函数的模拟实现中,要解决的其中一个难点就是如何将不同类型的数组,用一样的代码来处理,
首先就要从参数部分下手,参数部分我们要将指针的类型设置成 void*
,这样所有类型的数组我们都能进行接收,
接下来就是处理数组的拷贝问题,void*
的指针是不能直接进行解引用的,这意味着我们必须要转换指针类型,
这时我们的参数还有字节数,这就说明在我们将 void*
指针类型强转为 char*
后,可以根据传入的字节数大小去兼职循环的次数。
所以对于拷贝的操作我们只需将指针强转,然后解引用赋值即可,但是我们仍然要解决指针的移动问题,
void*
类型的指针是不能进行自增的,所以这也意味着我们要进行强转,但是 *(char *)dst++
强转后的自增是不可以的,
这样的代码相当于没有强转,所以我们只能采用强转赋值的方法去进行处理, dst = (char *)dst + 1;
,这样模拟实现的难点就都解决了。
2. memmove 使用和模拟实现
void * memmove ( void * destination, const void * source, size_t num );
memmove
内存移动函数,用于将源数组的内容拷贝放到目标数组中,与 memcopy
函数不同的是,
在 C语言 规定中 memmove
函数可以处理内存重叠部分的内容移动。
下面是使用 memmove
函数的一些注意事项:
memcpy
的差别就是memmove
函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用
memmove
函数处理。
函数使用示例:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1+2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
memmove
的模拟实现:
void * memmove ( void * dst, const void * src, size_t count)
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count)) {
/*
* Non-Overlapping Buffers
* copy from lower addresses to higher addresses
*/
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else {
/*
* Overlapping Buffers
* copy from higher addresses to lower addresses
*/
dst = (char *)dst + count - 1;
src = (char *)src + count - 1;
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return(ret);
}
对于 memmove
函数的模拟实现,难点在于我们如何处理重叠区域的内存移动,
我们先来分析在不同的情况下,重叠区域应该如何来移动:
在源数组与目标数组重叠部分在元素组偏后的元素时,我们如果先将源数组的内容从前往后放置到目标数组中,当 2 覆盖 5 后,
我们如果要将源数组的 5 覆盖目标数组的 8 时,我们就会发现此时 5 已经变成 2 ,无法达到我们的我目的,
所以我们要将源数组的内容从后往前放置到目标数组中,先将重叠区域的内容放置到目标数组中,避免被覆写后值改变。
所以目标数组与源数组重叠区域在源数组元素偏后时,我们就从后往前覆写。
跟上面一样进行分析后,当目标数组与源数组重叠区域在源数组元素偏前时,我们就从前往后覆写,避免重叠区域先被覆写。
当目标数组与源数组没有任何交集时,选择从前往后、从后往前的覆写方式均可以。这个难点被解决后,memmove
的模拟便再无难点
3. memset 函数的使用
void * memset ( void * ptr, int value, size_t num );
memset
是用来设置内存的,将内存中的值以字节为单位设置成想要的内容。
函数使用示范:
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "hello world";
memset (str,'x',6);
printf(str);
return 0;
}
对于 memset
函数在使用时我们一定要注意一点,对于这样的代码:
int arr[10] = {0};
memset (arr, 1, 4);
我们可千万不能认为是将 arr
数组的前四个元素置为 1 ,因为 memset
函数是以字节为单位进行设置,
所以是将 arr
数组的前四个字节全部置 1 ,也就是第一个元素的四个字节全部置 1,最后的结果为 16843099。
4. memcmp 函数的使用
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
比较从
ptr1
和ptr2
指针指向的位置开始,向后的num个字节返回值如下:
函数使用示例:
#include <stdio.h>
#include <string.h>
int main()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n;
n = memcmp(buffer1, buffer2, sizeof(buffer1));
if (n > 0)
printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' is less than '%s'.\n", buffer1, buffer2);
else
printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
return 0;
}
关于 memcpy
函数在使用时我们要注意下面的代码:
int arr[2] = {1, 2};
int arr1[3] = {1, 2, 3};
memcpy(arr, arr1, 9)
当函数比较第 9 个字节时,由于数组 arr
只开辟了 8 字节大小的空间,所以这时我们并不知道在数组 arr
后一个字节存储的内容,
这时系统仍然会根据内存中的内容进行比较,所以要避免写出这样的代码。