文章目录
- 前言
- Ⅰ 内存函数
- ⒈memcpy
- ⒉memmove
- ⒊memcmp
- ⒋memset
- Ⅱ 模拟实现
- ⒈模拟实现 memcpy
- ⒉模拟实现 memmove
- ⒊模拟实现 memcmp
- ⒋模拟实现 memset
前言
内存操作函数的优势
- 字符串函数只能操作字符串的内容,局限性很大。
- 而内存函数可以操作任意类型的数据,因为是直接对内存进行操作。
函数引用头文件
- <string.h>
Ⅰ 内存函数
⒈memcpy
内存拷贝函数
void * memcpy ( void * destination, const void * source, size_t num );
函数参数
- destination:指向要粘贴的目标空间的起始地址,类型转换为 void* 类型的指针。
- source:指向要被拷贝的源头空间的起始地址,类型转换为 const void* 类型的指针。
- num:要拷贝的字节数。
函数功能
- 从 source 指向的目标空间拷贝 num 个字节的内容到 destination 的内存位置。
- memcpy 在遇到 \0 时不会停止。
- 如果 source 和 destination 有任何内存上的的重叠,复制的结果都是未定义的。
- strcpy 只能拷贝的对象只能是字符串,而 memcpy 直接拷贝内存中得内容,就使得 memcpy 能拷贝的数据类型变得更多。
返回值
- 目标空间的起始地址。
函数用例
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
int arr1[10] = { 0 };
int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr2) / sizeof(arr2[0]);
memcpy(arr1, arr2, sz * sizeof(arr2[0]));
//从 arr2 中拷贝 10 * 4 个字节的内容到 arr1 去
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
putchar('\n');
return 0;
}
不能处理内存重叠的情况
- 如果源空间和目标空间在内存中出现重叠情况的话,拷贝的内容就会出现重叠的情况。
- 此时就只能使用 memmove 函数了。
- 可能会有人使用 memcpy 去尝试重叠拷贝,发现能够正确处理这种情况。
- 在 C 语言中,memcpy 只处理不重叠的拷贝。在 VS 中,一旦在使用 memcpy 拷贝时发现重叠的情况,会自动将 memcpy 替换成 memmove。
⒉memmove
处理内存重叠的情况
void * memmove ( void * destination, const void * source, size_t num );
函数功能
- 和 memcpy 的差别就是 memmove 函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用 memmove 函数处理。
函数用例
⒊memcmp
内存比较函数
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
函数功能
- 比较 ptr1 和 ptr2 所指向空间的前 num 个字节的内容。
函数返回值
- 函数的返回值和 strcmp 的情况一样,在前 num 个字节里:
函数返回值 | 说明 |
---|---|
0 | ptr1 和 ptr2 所指向的两块空间的内容完全一致 |
< 0 | 在两块空间中首次出现不相等的数据时,ptr1 指向的数据小于 ptr2 的 |
> 0 | 在两块空间中首次出现不相等的数据时,ptr1 指向的数据大于 ptr2 的 |
函数用例
⒋memset
void * memset ( void * ptr, int value, size_t num );
函数功能
- 将 ptr 指向的空间的前 num 个字节的内容设置成给定的 value。
函数用例
内存设置函数
Ⅱ 模拟实现
⒈模拟实现 memcpy
void* my_memcpy(void* dest, const void* sour, size_t num)
{
assert(dest && sour);
void* start = dest; //保留目标空间的起始地址
for (int i = 0; i < num; i++) //总共拷贝 num 个字节的内容
{
*(char*)dest = *(char*)sour; //一次拷贝一个字节的内容过去
dest = (char*)dest + 1; //让 dest 指向下一个字节的位置
sour = (char*)sour + 1; //让 sour 指向下一个字节的位置
}
return start; //返回目标空间的起始地址
}
⒉模拟实现 memmove
实现思路
- 内存重叠的时候要避免一开始就把重叠部分的数据给修改。
- 所以当两块空间产生重叠的时候就有两种处理方法了。
- 从 sour 末尾向前拷贝数据到 dest。
- 从 sour 靠头向后拷贝数据到 dest。
1. 从后向前拷贝
- 当 dest 的起始位置落在 sour 指向空间的右边时可以让 sour 使用从后向前拷贝的方法。
- 如:dest[4] = sour[4] ——> dest[3] = dest[3] ——> dest[2] = sour[2] …… 从尾开始向前拷贝。
2. 从前向后拷贝
- 如果使用从后向前拷贝的话,上面那种情况 dest 的起始位置落在 sour 指向空间的右边很容易就能解决。
- 但是如果 dest 的起始位置落在 sour 指向空间的左边就不是那么一回事了。
判断拷贝方式
- 根据 dest 的落点来判断使用(从后向前 / 从前向后)的拷贝方式。
- dest 的落点可以划分成三个部分
- dest 起始位置落在 sour 指向的空间前:从 sour 的开头(从前向后)拷贝到 dest。
- dest 起始位置落在 sour 指向的空间中:从 sour 的末尾(从后向前)拷贝到 dest。
- dest 起始位置落在 sour 指向的空间后:从 sour 的末尾(从后向前)拷贝到 dest。
代码实现方法
-
当 dest 的起始位置落在 sour 的各个区域时,可以选择的划分方式。
- 法 1:1 区和 3 区选择(从前向后)拷贝,2 区选择(从后向前)拷贝。
- 法 2:2 区和 3 区选择(从后向前)拷贝,1 区选择(从前向后)拷贝。
-
个人推荐使用法 2,因为只需要判断 dest 的起始地址在 sour 的起始地址的左右方向即可。
法 2 展示代码
void* my_memmove(void* dest, const void* sour, size_t num)
{
assert(dest && sour);
void* start = dest;
if (dest < sour) //dest 落在 sour 左边,选择 前 -> 后
{
for (int i = 0; i < num; i++) //从前向后拷贝就是模拟实现 memcpy 里的那段代码
{
*(char*)dest = *(char*)sour;
dest = (char*)dest + 1;
sour = (char*)sour + 1;
}
return start;
}
else //dest 落在 sour 右边,选择 后 -> 前
{
while (num--) //从 sour 指向空间的最后一个字节开始向前拷贝
{
*((char*)dest + num) = *((char*)sour + num);
}
}
return start; //返回目标空间的起始地址
}
⒊模拟实现 memcmp
int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
assert(ptr1 && ptr2);
while (num--) //比较两块空间的前 num 个字节
{
if (*(char*)ptr1 == *(char*)ptr2) //当前字节相等时继续比较下一对字节
{
ptr1 = (char*)ptr1 + 1;
ptr2 = (char*)ptr2 + 1;
}
else //出现不相等时返回两对字节之间的差值
{
return *(char*)ptr1 - *(char*)ptr2;
}
}
return 0;
}
⒋模拟实现 memset
void* my_memset(void* ptr, int value, size_t num)
{
assert(ptr);
char* start = ptr;
for (int i = 0; i < num; i++)
{
*((char*)ptr + i) = (char)value;
}
return start;
}