C语言中的内存函数
目录
- 1 memcpy()函数的基本信息及功能
- (1) void * destination
- (2) const void * source
- (3) size_t num
- 1.2 memcpy()函数实战演练
- 1.3 memcpy()函数的模拟实现
- 1.3.1 my_memcpy()函数定义及参数
- 1.3.2 my_memcpy()函数函数体
- 1.3.3 my_memcpy()函数拷贝元素堆叠
- 2 memmove()函数的基本信息及功能
- 2.1 memmove()函数实战演练
- 2.2 memmove()函数的模拟实现
- 2.2.1 my_memmove()函数定义及参数
- 3 memset()函数的基本信息及功能
- (1)void * ptr
- (2)int value
- (3)size_t num
- 3.1 memset()函数实战演练
- 3.2 memset()函数能否把整型数组中的元素全部替换为1
- 4 memcmp()函数的基本信息及功能
- (1)const void * ptr1
- (2)const void * ptr2
- (3)size_t num
- 4.1 memcmp()函数实战演练
- 4.1.1 memcmp()不提前结束
- 4.1.2 memcmp()提前结束
1 memcpy()函数的基本信息及功能
我们从cplusplus官网(cplusplus(C语言函数查询网站)上我们不难看到memcpy()函数是有三个参数如图:
memcpy函数拷贝结束后,会返回目标空间的起始地址类型是(void *)
1、函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。
2、该函数在遇到’\0’字符时不会停止执行。需要注意的是,与strcpy()函数不同,strcpy()专门用于字符串的复制操作,而memcpy()则适用于任意数据类型的复制。
下面我们对每个参数进行详细解释。
(1) void * destination
调用该函数时,需将目的地数组的地址作为第一个参数传入,该地址将由 void * 类型的指针destination 接收。需要注意的是,void * 类型的指针是一种无具体类型的指针,能够接受任何数据类型的地址,因此 memcpy() 函数可以实现任意数据类型之间的拷贝。
(2) const void * source
将源数组的地址作为第二个参数传入,该地址由 const void * 类型的指针 source 接收(const 位于 * 左侧,表示不能对指针进行解引用操作)。
(3) size_t num
1、要复制的字节数。
2、size_t 是无符号整型。
1.2 memcpy()函数实战演练
我们下面使用memcpy()函数进行一个整型数组的拷贝,两个数组如下:
int arr1[] = { 1, 2, 3, 4, 5 , 6, 7, 8, 9, 10 };//源头int arr2[20] = { 0 };//目的地
接下来我们将调用标准库中的memcpy()函数,实现数组元素的复制作。具体操作是将arr1数组的前5个元素复制到arr2数组中。调用时需传入三个参数:
目标数组arr2、源数组arr1,以及要复制的字节数5*sizeof(int)。这里需要注意的是,字节数等于要复制的元素数量乘以每个元素所占的字节大小。
完整代码如下:
#include <stdio.h>
#include <string.h>
int main() {int arr1[] = { 1, 2, 3, 4, 5 , 6, 7, 8, 9, 10 };//源头int arr2[20] = { 0 };//目的地//目标将arr1数组中的前5个元素拷贝到arr2数组中//memcpy - 针对内存块进行拷贝//memcpy函数拷贝结束后,会返回目标空间的起始地址int* ret = memcpy(arr2, arr1, 5*sizeof(int));int i = 0;for (i = 0; i <= 19; i++) {printf("%d ", *(ret+i));}return 0;
}
运行结果:
1.3 memcpy()函数的模拟实现
掌握了memcpy()函数的基本用法后,接下来我们将尝试模拟实现它,编写一个自定义的my_memcpy()函数。
1.3.1 my_memcpy()函数定义及参数
首先,设计函数的三个参数, 因为要模拟实现 memcpy()函数,因此直接 仿照memcpy()函数的参数即可:
void* my_memcpy(void* dest, const void* src, size_t num)
1.3.2 my_memcpy()函数函数体
//memcpy()函数的模拟实现
void* my_memcpy(void* dest, const void* src, size_t num) {void* ret = dest;assert(dest && src);while (num--){*(char *)dest = *(char *)src;//指针加1的第一种写法//dest = (char*)dest + 1;//src = (char*)src + 1;指针加1的第二种写法((char* )dest)++;((char*)src)++;//(char *)dest++错误写法,dest指针只是临时转换为char * 指针并没有使用,本质还是void *类型指针,无法进行操作}return ret;
}
为了实现任意数据类型的拷贝,由于无法预知dest 和 src参数的具体类型,我们将其声明为 void * 类型。 void * 是一种无具体类型的指针,能够接收任何类型的地址。然而,由于 void * 指针无法直接解引用或进行整数加减运算,我们需要将其统一强制转换为 char * 类型。这种转换使得我们可以将内存视为按字节操作的空间,从而逐字节进行拷贝。
在拷贝过程中,我们需要处理 num 个元素。由于 char * 类型的指针每次解引用只能操作(拷贝)一个字节,因此外层需要使用 while 循环,循环次数为待拷贝的元素个数乘以每个元素的大小。
该部分完整代码如下:
#include <stdio.h>
#include <assert.h>
//memcpy()函数的模拟实现
void* my_memcpy(void* dest, const void* src, size_t num) {void* ret = dest;assert(dest && src);while (num--){*(char *)dest = *(char *)src;//指针加1的第一种写法//dest = (char*)dest + 1;//src = (char*)src + 1;指针加1的第二种写法((char* )dest)++;//(char *)dest++错误写法,dest指针只是临时转换为char * 指针并没有使用,本质还是void *类型指针,无法进行操作((char*)src)++;}return ret;
}
int main() {int arr1[] = { 1, 2, 3, 4, 5 , 6, 7, 8, 9, 10 };//源头int arr2[20] = { 0 };//目的地//my_memcpy(arr2, arr1, 20);//数组下标法int *ret = my_memcpy(arr2, arr1, 20);//指针法打印int i = 0;int size = 0;size = sizeof(arr2) / sizeof(arr2[0]);//计算数组的大小//打印方法(数组下标进行打印): //for ( i = 0; i < size; i++)//{// printf("%d ", arr2[i]);//}//打印方法(用函数返回值——>指针进行打印)for (i = 0; i <= 19; i++) {printf("%d ", *(ret+i));}return 0;}
1.3.3 my_memcpy()函数拷贝元素堆叠
如果source和destination有任何的重叠,复制的结果都是未定义的。
//探究 ——> 如果source和destination有任何的重叠,复制的结果都是未定义的。
void* my_memcpy(void* dest, const void* src, size_t num)
{void* ret = dest;int i = 0;assert(dest && src);while (num--){*(char*)dest = *(char*)src;((char*)src)++;((char*)dest)++;}return ret;
}int main()
{int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };my_memcpy(arr1 + 2, arr1, 20);//arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }//预期效果: 1 2 1 2 3 4 5 8 9 10//实际效果: 1 2 1 2 1 2 1 8 9 10int i = 0;for ( i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}
该情况如图
运行结果:
当源空间和目标空间出现重叠时,需要使用接下来介绍的memmove()函数。
2 memmove()函数的基本信息及功能
我们从cplusplus官网(cplusplus(C语言函数查询网站)上我们不难看到memmove()函数是有三个参数如图:
memmove() 函数的参数与memcpy() 函数 完全一致,两者的主要区别在于 memmove() 能够正确处理源内存块和目标内存块重叠的情况。
Tips:如果源空间和⽬标空间出现重叠,就得使用memmove函数处理。
2.1 memmove()函数实战演练
我们下面使用memmove()函数进行一个整型数组(源空间和⽬标空间出现重叠)的拷贝,如下:
//memcpy()函数实战演练
int main()
{int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };memmove(arr + 2, arr, 5 * sizeof(int));int i = 0;for ( i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}
这两个数组在拷贝时存在元素重叠的情况。
arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
预期效果:1 2 1 2 3 4 5 8 9 10
堆叠效果:1 2 1 2 1 2 1 8 9 10
这个时候,我们就要用到memcpy() 函数进行拷贝
运行结果:
2.2 memmove()函数的模拟实现
掌握了memmove()函数的基本用法后,接下来我们将尝试模拟实现它,编写一个自定义的my_memmove()。
2.2.1 my_memmove()函数定义及参数
首先,设计函数的三个参数, 因为要模拟实现memmove()函数,因此直接 仿照memmove()函数的参数即可:
void* my_memcpy(void* dest, const void* src, size_t num)
my_memmove()函数的实现思路与my_memcpy()函数基本一致,但增加了对内存重叠情况的处理。根据源地址与目标地址的相对位置,主要存在以下三种重叠情形:
情况一:src > dest(内存有重叠)
情况二:src < dest(内存有重叠)
情况三:src < dest(内存无重叠)
综上我们可以把上述三种情况总结为:
1、 当目标地址位于图中区域1时,采用从前向后的顺序进行拷贝;
2、当目标地址位于图中区域2时,采用从后向前的顺序进行拷贝;
3、当目标地址位于图中区域3时,可选择从前向后或从后向前的顺序进行拷贝。
因为,数组随着下标的增加地址是由低到高变化的。于是,我们判断重叠机制就有以下两种写法:
第一种写法:
若目标地址 dest 小于源地址 src,则从前向后执行拷贝操作;反之,则从后向前进行拷贝。
代码如下:
void* my_memcpy(void* dest, const void* src, size_t num) {void* ret = dest;assert(dest && src);if (dest < src)//从前往后进行拷贝{while (num--){*((char*)dest) = *((char*)src);dest = (char*)dest + 1;src = (char*)src + 1;}}else//从前往后进行拷贝{while (num--) {*((char*)dest + num) = *((char*)src + num);}}return ret;
}
第二种写法:
当 dest 地址位于 src 地址之前,或超过 src 加上拷贝元素个数的地址范围时,采用从前向后的拷贝方式;其余情况则一律从后向前进行拷贝。
代码如下:
//方法2
void* my_memcpy(void* dest, const void* src, size_t num) {void* ret = dest;assert(dest && src);if (dest < src || dest >= (char *)src + num)//从前往后{while(num--){*((char*)dest) = *((char*)src);dest = (char*)dest + 1;src = (char*)src + 1;}}else//从后往前{while (num--){*((char*)dest + num) = *((char*)src + num);}}return ret;
}
如何进行从后到前的拷贝呢?关键是要找到源数组和目标数组的最后一个,元素下面通过图示进行讲解。假设需要将源数组中的5个元素拷贝到目标数组中。
如图所示,当需要拷贝5个元素时,我们只需将src指针向后移动5 * sizeof(int) - 1次,即可定位到待拷贝元素的最后一个位置。同理,dest指针经过相同次数的移动后,也能指向目标位置的最后一个元素(5个元素中的最后一个元素)。
完整代码如下:
//方法一
void* my_memcpy(void* dest, const void* src, size_t num) {void* ret = dest;assert(dest && src);if (dest < src)//从前往后进行拷贝{while (num--){*((char*)dest) = *((char*)src);dest = (char*)dest + 1;src = (char*)src + 1;}}else//从前往后进行拷贝{while (num--) {*((char*)dest + num) = *((char*)src + num);}}return ret;
}//方法2
//void* my_memcpy(void* dest, const void* src, size_t num) {
// void* ret = dest;
// assert(dest && src);
// if (dest < src || dest >= (char *)src + num)//从前往后
// {
// while(num--)
// {
// *((char*)dest) = *((char*)src);
// dest = (char*)dest + 1;
// src = (char*)src + 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 };//使用自己的memcpy()函数my_memcpy(arr + 5, arr , 5 * sizeof(int));int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}
3 memset()函数的基本信息及功能
memset()函数 是⽤来设置内存的,将内存中的值以字节为单位设置成想要的内容。
我们从cplusplus官网(cplusplus(C语言函数查询网站)上我们不难看到memset()函数是有三个参数如图:
(1)void * ptr
调用该函数时,需将待填充内存块的地址作为首个参数传递给ptr指针。由于void *类型指针是一种通用指针,能够接收任意数据类型的地址,因此memset()函数能够以字节为单位,将任意数据类型的内存内容设置为指定值。
(2)int value
(3)size_t num
3.1 memset()函数实战演练
我们下面使用memset()函数进行一个应用,如下:
#include <stdio.h>
#include <string.h>
int main() {char arr[] = "hello world";memset(arr + 2, 'y', 7);printf("%s\n", arr);return 0;
}
该代码将从arr地址偏移2个单位的位置开始,将后续7个元素全部赋值为’y’。
运行结果
3.2 memset()函数能否把整型数组中的元素全部替换为1
目的:想要通过memset()函数将整型数组中的元素全部替换为1
代码如下:
//探讨memset()函数能否把整型数组中的元素全部替换为1
int main() {int arr[5] = { 1, 2, 3, 4, 5 };memset(arr, 1, 20);//以字节为单位设置的int i = 0;for ( i = 0; i < 5; i++){printf("%d ", arr[i]);}return 0;
}
要理解这个问题,首先需要明确数据是以二进制补码形式存储在内存中的。整型数据占用4个字节,即32个比特位。由于正数的原码、反码和补码完全一致,因此我们可以直接得出arr数组中各数字对应的补码表示。
4个二进制数对应一个16进制数,下方为对应的16进制数。
由于内存单元以字节为单位,我们可以通过调试工具直观地观察数据在内存中的存储方式。
由于 memset() 函数以字节为单位对内存进行赋值操作,执行上述代码后,每个字节都将被设置为 1。
内存中:
我们观察到,确实每个字节的内容都被设置成了1
运行结果:
结论:memset()函数无法将整型数组中的所有元素统一设置为1,它仅能将每个字节替换为1。
4 memcmp()函数的基本信息及功能
memcmp()函数比较从ptr1和ptr2指针指向的位置开始,向后的num个字节。
我们从cplusplus官网(cplusplus(C语言函数查询网站)上我们不难看到memcmp()函数是有三个参数如图:
该函数的返回值:
(1)如果 * ptr1(即ptr1指向的数据)小于 * ptr2(即ptr2指向的数据),则返回一个小于0的整数(例如-1)。
(2)如果 * ptr1等于 * ptr2,则返回0。
(3)如果 * ptr1大于 * ptr2,则返回一个大于0的整数(例如1)。
(1)const void * ptr1
ptr1 是一个 const void * 类型的指针,它指向待比较的第一个内存块的起始地址。const 修饰符位于 * 号的左侧,这意味着 ptr1 所指向的内存内容是不可修改的,即通过 ptr1 无法对内存块中的数据进行任何写操作。
(2)const void * ptr2
与ptr1类似,ptr2指向第二个待比较内存块的起始地址。
(3)size_t num
比较 ptr1 和 ptr2 指向的内存区域时,需要向后比较 num 个字节。但在比较过程中,若提前发现大小差异,即可终止比较,否则需完整比较全部 num 个字节。
4.1 memcmp()函数实战演练
4.1.1 memcmp()不提前结束
该情况是memcmp()函数会逐字节比较数据,直到达到指定的num字节数为止。
#include <stdio.h>
#include <string.h>
int main() {int arr1[] = { 1, 2, 3, 4, 5, 6, 7 };int arr2[] = { 1, 2, 3, 4, 5, 8, 8 };int ret = memcmp(arr1, arr2, 20);printf("%d\n", ret);return 0;
}
运行结果
4.1.2 memcmp()提前结束
该情况是memcmp()函数会逐字节比较数据,但是未到达到指定的num字节数为止。这是因为在这之前就发现了大小关系!
#include <stdio.h>
#include <string.h>
int main() {int arr1[] = { 1, 2, 3, 4, 5, 6, 7 };int arr2[] = { 1, 2, 3, 4, 5, 8, 8 }; int ret = memcmp(arr1, arr2, 7 * sizeof(int));printf("%d\n", ret);return 0;
}
运行结果:
以上就是关于内存函数及其部分模拟实现的全部内容,希望能对大家有所帮助或有所启发,感谢各位小伙伴的耐心阅读。咱们下期见!拜拜~