memcpy的使用和模拟实现
memcpy的使用
memcpy函数的原型声明如下:
void * memcpy (void * restrict destination, const void * restrict source, size_t num);
这里进行一下详细的解读:
1. `void * destination`:这是一个指向目标内存区域的指针,即将接收拷贝内容的地址。
2. `const void * source`:这是一个指向源内存区域的常量指针,即需要被拷贝内容的起始地址。
3. `size_t num`:这是一个无符号整型变量,表示要拷贝的字节数。这意味着无论源内存区域中存储的是何种类型的数据,都将以字节为单位进行拷贝。
此外,使用了C99中的`restrict`关键字,它表明在指定的作用域内,指针destination和source分别指向不同的内存区域,不会有重叠部分,这样编译器可以进行更有效的优化。
因为如果有内存重叠,那么拷贝的结果就会不正确
memcpy函数实现了从源内存区域到目标内存区域的指定字节数的复制操作,适用于各种数据类型的内存块复制。
memcpy与strcpy很相似,只是strcpy只能对字符串进行操作,而memcpy可以对任意类型的数据进行操作。
使用方式如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 0,0,0,0,0 };
memcpy(arr1, arr2, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
在内存操作中,我们执行了一个memcpy操作,将arr2数组的前20个字节内容复制到arr1数组内。鉴于整型变量通常占用4个字节,所以这实际上涉及到了拷贝5个整数(因为5 * 4 = 20)。因此,在成功执行memcpy操作后,arr1数组的前5个元素将会被赋值为arr2数组相应位置的整数值。
还有一个点就是:函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置
memcpy的模拟实现
我们可以自己动手来实现一个memcpy函数,代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void* my_memcpy(void* dest, void* src, size_t num)
{
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 0,0,0,0,0 };
my_memcpy(arr1, arr2, 12);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
在执行内存复制操作时,考虑到`memcpy`函数会返回目标内存区域(destination)的地址,为了便于后续可能的操作或验证,我们可以定义一个指向destination的指针变量——例如命名为`ret`,用以存储memcpy函数的返回值。
在循环复制过程中,我们设置条件为`num--`,这意味着循环将执行12次,这与所需复制的字节数相匹配。由于此次操作是对单个字节进行精确复制,因此需要首先将源和目标指针转换为`char`类型,以便逐字节地进行访问和复制。
在每一次循环迭代中,我们将一个字节从源地址复制到目标地址,并随后递增`dest`和`src`指针各一位,确保它们指向下一个待复制的字节位置。如此反复执行12次循环后,即可完成对原始整型数据的逐字节复制过程。
memmove的使用和模拟实现
memmove的使用
在讨论内存操作时,我们曾提及memcpy
函数的一个限制,即它无法安全地处理内存区域重叠的情况。若两个数组或内存区域存在重叠部分,在这种情况下直接调用memcpy
可能会导致数据损坏。为了应对这一问题,C语言提供了另一个函数——memmove
。
memmove
函数具备与memcpy
相似的功能,可以将源内存区域的内容复制到目标内存区域,但其独特之处在于,即使源和目标区域存在重叠,也能确保正确无误地完成拷贝任务。以下是memmove
函数的使用方法
void * memmove (void * destination, const void * source, size_t num);
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 1,2,3,4,5 };
memmove(arr1 + 2, arr2, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
这段代码利用`memmove`函数处理了两个整数数组`arr1`和`arr2`之间的内存重叠拷贝问题。具体而言,它将`arr2`数组的内容从`arr1`数组的索引为2的位置开始进行插入式拷贝。执行该操作后,`arr1`数组变为{1, 2, 1, 2, 3, 4, 5, 8, 9, 10}。
尽管在执行拷贝时,源数组(`arr2`)与目标数组(`arr1`)的部分区域存在重叠,但`memmove`函数依然能够确保正确地完成复制任务,即使面对这样的复杂情况也能安全、有效地处理数组元素的移动和覆盖。
memmove的模拟实现
下面我们来进行memmove的模拟实现,在实现之前,我们来对内存重叠的情况进行分析,有以下几种情况:
在处理内存区域重叠问题时,当目标地址(`dest`)位于源地址(`src`)之前时,我们可以利用从前往后复制的策略。这种情况下,由于复制操作是从较低地址向较高地址进行,因此不会遇到因覆盖未复制数据而导致的错误。例如,在使用`memmove`函数时,即使两个内存区域存在重叠,它也能根据地址关系智能选择合适的复制方式,确保数据正确无误地被复制到目标区域。在这种特定场景下,即便不使用`memmove`而采用简单的循环逐字节复制,也不会出现内存重叠问题。
第二种情况如下:
在面对内存区域重叠问题时,若目标地址(`dest`)位于源地址(`src`)之后,采用从后往前的复制策略是一种有效避免因重叠引发数据错误的方法。这意味着,在执行复制操作时,我们将首先复制源内存区域中较高地址的内容,再逐步处理较低地址的数据,确保已复制的数据不会在后续步骤中被新复制的数据覆盖。
在这种情况下,即使不使用特别设计用于处理内存重叠情况的`memmove`函数,而通过自定义循环实现从后往前的逐字节或逐元素复制,也能确保数据复制的正确性与完整性。
基于上述两种方法,我们可以写出以下代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void* my_memmove(void* dest, void* src, size_t num)
{
void* ret = dest;
while (num--)
{
if (dest < src)//前->后
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
else //后->前
{
*((char*)dest + num) = *((char*)src + num);
dest = (char*)dest - 1;
src = (char*)src - 1;
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 1,2,3,4,5 };
my_memmove(arr1+2, arr2, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
这段代码采用了两种不同的复制策略,巧妙地解决了内存区域重叠时的复制问题。在面对可能存在的内存区域重叠场景时,代码根据目标地址(`dest`)与源地址(`src`)之间的相对位置关系,分别采用从前到后和从后往前的复制方法。
首先,在目标地址位于源地址之前的情况中,代码选择从前往后的顺序进行复制操作。这种策略下,复制过程按照地址的增长顺序进行,即从较低地址向较高地址逐个字节或逐个元素地复制数据。由于复制是有序且递增的,即使存在重叠也不会覆盖尚未复制的数据,从而确保了数据的完整性。
其次,在目标地址位于源地址之后的情形下,代码采取了从后往前的复制方式。这意味着复制操作开始于源内存区域的高地址部分,并逐渐向低地址移动。这样,在处理可能存在重叠的区域时,已复制过的较高地址内容不会被随后复制的较低地址内容所覆盖,因此同样保证了数据的安全性。
通过这两种针对性的复制策略,该段代码有效地规避了因内存重叠导致的复制错误问题,确保了不论何种情况下都能准确无误地完成内存区域间的复制任务。
memset的使用
memset的使用
memset是一个C库函数,用来给一块内存区域设置值。它的原型为:
void *memset(void *s, int c, size_t n);
其中,s是要设置值的内存区域的指针,c是要设置的值,n是要设置的字节数。
使用memset的步骤如下:
- 引入头文件。
要使用memset函数,需要引入<string.h>头文件。
#include <string.h>
- 声明一个内存区域。
可以使用数组、指针或者动态内存分配来声明一个内存区域。例如:
char str[50];
- 调用memset函数。
使用memset函数来设置内存区域的值。例如:
memset(str, 'A', sizeof(str));
上面的代码将str数组中的每个字节都设置为'A'。
- 检查结果。
可以通过输出数组或者查看内存区域的值来检查memset的结果。例如:
printf("%s\n", str);
上面的代码将输出"A",因为str数组中的每个字节都被设置为'A'。
需要注意的是,memset函数是按字节设置值的,不能用来设置非字符类型的数据。此外,如果要设置的是指针或者结构体等复杂类型的数据,可能会出现未定义的行为。
另外,memset函数的返回值是指向被设置的内存区域的指针。
memcmp的使用
memcmp的使用
memcmp是一个C库函数,用来比较两个内存区域的值。它的原型为:
int memcmp(const void *s1, const void *s2, size_t n);
其中,s1和s2分别是要比较的两个内存区域的指针,n是要比较的字节数。
使用memcmp的步骤如下:
- 引入头文件。
要使用memcmp函数,需要引入<string.h>头文件。
#include <string.h>
- 准备要比较的内存区域。
可以使用数组、指针或者动态内存分配来声明要比较的内存区域。例如:
int array1[] = {1, 2, 3, 4, 5};
int array2[] = {1, 2, 3, 4, 6};
- 调用memcmp函数。
使用memcmp函数来比较两个内存区域的值。例如:
int result = memcmp(array1, array2, sizeof(array1));
上面的代码比较了array1和array2中的每个字节的值。
- 检查比较结果。
根据memcmp函数的返回值来判断两个内存区域的值是否相等。返回值为0表示两个内存区域的值相等,大于0表示s1大于s2,小于0表示s1小于s2。例如:
if (result == 0) {
printf("两个内存区域的值相等\n");
} else if (result > 0) {
printf("s1大于s2\n");
} else {
printf("s1小于s2\n");
}
上面的代码根据result的值输出相应的结果。
需要注意的是,memcmp函数是以字节为单位进行比较的,并且比较的是对应字节的ASCII值。因此,如果要比较的内存区域中包含非字符类型的数据,可能得到不符合预期的比较结果。对于结构体等复杂类型的数据,也可能出现未定义的行为。