C语言(内存函数)

小羊在奋斗 2024-06-12 12:05:04 阅读 94

        Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,欢迎欢迎~~     

                                                💥个人主页:小羊在奋斗

                                                💥所属专栏:C语言   

        本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为一些学友们展示一下我的学习过程及理解。文笔、排版拙劣,望见谅。 

                                1、memcpy 的使用和模拟实现

                                2、memmove 的使用和模拟实现

                                3、memset 函数的使用

                                4、memcmp 函数的使用

1、memcpy 的使用和模拟实现

        1.1  memcpy 函数的使用 

        memcpy 前面的 mem 指的是 memmory ,英文单词“记忆”,在C语言中指的是内存。后面要介绍的 memmove、memset 和 memcmp 都是如此。

        memcpy 是一个内存拷贝函数,其作用是将一个内存区域内指定的 count 个字节大小的内容拷贝到目标内存空间内。值得注意的是,虽然 memcpy 是一个内存函数,但其是定义在 <string.h>头文件内的。

         上面关于 memcpy 函数的作用及其用法的描述还是很好理解的,这里再做一些说明。

        (1)为了使 memcpy 函数可以实现对任意类型的内容拷贝,其参数定义为了 void *类型的指针;

        (2)跟之前学过的字符串相关的函数一样,memcpy 函数拷贝的目标空间必须是可修改的,而被拷贝的内存区域可用 const 修饰;

        (3)当 source 和 destination 有任何重叠的时候,复制的结果都是未定义的,也就是说memcpy 函数不负责重叠内存的拷贝。

        这个函数还是比较简单的。

       1.2  memcpy 函数的模拟实现

        memcpy 函数和 strcpy 函数有相似的地方,所以其实现的逻辑也就应该和 strcpy 函数的模拟实现类似。首先我们需要搞清楚,为了实现对任意类型的内容拷贝,我们将 memcpy 函数的参数及其返回值都设定成了 void * 类型的指针,但是 void * 类型的指针有缺点,不能直接进行解引用,也不能对其进行 +- 操作,那我们就要想一个办法解决这个问题。

        其实这个问题我们之前在模拟实现 qsort 函数的时候就有了一个解决办法,就是将其强转为char * 类型的指针,因为 char * 类型的指针指向的对象大小是一个字节,是所有类型中字节大小最小的,不管对象类型是多大字节,只要一个字节一个字节地拷贝,就可以对实现对任意类型的内容拷贝了。

        那有了上面的思路,我们就能写出下面的代码:

#include <stdio.h>#include <assert.h>void* my_memcpy(void* dest, const void* sour, size_t count){assert(dest && sour);void* pd = dest;while (count--)//控制拷贝多少个字节{*(char*)dest = *(char*)sour;((char*)dest)++;((char*)sour)++;}return pd;//返回目标空间的起始地址}void text1(){int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[20] = { 0 };int* pi = my_memcpy(arr2, arr1 + 2, 20);for (int i = 0; i < 5; i++){printf("%d ", *(pi + i));}printf("\n");}void text2(){char str1[] = "abcdefghijklmnopqrstuvwxyz";char str2[20] = { 0 };char* ps = my_memcpy(str2, str1 + 5, 10);printf("%s\n", ps);}int main(){text1();text2();return 0;}

        跟之前我们模拟实现 qsort 函数相比,这就是张飞吃豆芽。 

        上面我们是将一个内存区域的内容拷贝到另一个内存区域,那能不能实现在一个内存区域内的拷贝呢?我们来试一下:

        可以看到,当我们在一个内存区域内拷贝,并且内存有重叠的时候,my_memcpy 函数就不能完成我们想要的结果了,这是因为重叠的部分已经被拷贝过来的内容代替,原内容就消失了,当拷贝到重叠的内存区域时,拷贝的还是之前拷贝过来的内容。不过只要内存不重叠,在一个内存区域内拷贝也是可行的。

        但是,我们可以看到 memcpy 函数并没有这个问题,那是我们模拟的函数有问题吗?其实不是的,memcpy 函数之所以没有这个问题,是因为在某些系统上,memcpy函数可能会检测是否源内存和目标内存有重叠,并采取一些措施以确保正确的结果。然而,这种行为是不可靠的,不同的编译器或系统的实现方式可能会导致不同的结果。

         上面的情况和我们在 字符、字符串函数 中介绍到的用 strcat 函数实现一个字符串自己拼接到自己末尾产生的问题是类似的,同样的 strcat 函数表面上虽然也没有什么问题,但是这种行为也是不可靠的。为了代码的可移植性和安全性,最好还是使用memmove 函数来处理重叠内存的情况。接下来我们就来介绍 memmove 函数。 

2、memmove 的使用和模拟实现

        2.1 memmove 函数的使用

        对比 memcpy 函数,memmove 函数与之是及其相似的,特别的是 memmove 函数操作的对象是可以重叠的,正如它所描述的它会将内容如同先复制到一个临时数组中,这样就解决了目标内存区域的内容被覆盖的问题。 

        2.1 memmove 函数的模拟实现 

        那么了解了 memmove 函数的逻辑,模拟实现它也不是什么难事。我们只需要创建一个临时数组过渡就行,于是就得到了下面的代码:

#include <stdio.h>#include <assert.h>void* my_memmove(void* dest, const void* sour, size_t count){assert(dest && sour);void* pd = dest;int i = 0;char arr[1000] = { 0 };for (i = 0; i < count; i++){arr[i] = *(char*)sour;((char*)sour)++;}for (i = 0; i < count; i++){*(char*)dest = arr[i];((char*)dest)++;}return pd;}int main(){int i = 0;int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr1) / sizeof(arr1[0]);my_memmove(arr1 + 2, arr1, 20);for (i = 0; i < sz; i++){printf("%d ", arr1[i]);}return 0;}

        是不是很简单呢,这样我们就实现了模拟 memmove 函数的功能。但是上面这种创建临时字符数组的办法有一点不足,因为我们并不能确定被拷贝的内容有多大,所以只能模糊地创建一个比较大的数组,但是这个比较大是多大没办法知道,创建大了浪费,创建小了不够,那有没有什么办法能解决这个问题呢? 

        2.3 memmove 函数的模拟优化

        既然我们并不能确定要创建一个多大的临时数组,那我们干脆放弃创建临时数组的方法另辟奇径。

        让我们再回到之前遇到的问题,如果内存重叠时拷贝会将原内容覆盖。那是不是我们拷贝的方法有问题呢?来看:

        将红色方框内的内容拷贝到蓝色方框内:

 

        我们发现,从前向后拷贝行不通,因为会覆盖掉还没拷贝的内容;但从后向前拷贝是可行的,并没有出现还没拷贝的内容被覆盖的情况。

        将蓝色方框内的内容拷贝到红色方框内:

        我们又发现,从后向前行不通,但从前向后是可行的。

        而之所以有时需要从前向后拷贝,有时需要从后向前拷贝,是取决于是将前面的内容拷贝到后面,还是将后面的内容拷贝到前面。

        前面介绍数组的时候我们说过,数组元素随着下标的增大地址逐渐增大。也就是说,如果上面需要将红色方框内的内容拷贝到蓝色方框内,那么当指针p1小于指针p2时,需要从后向前拷贝;当指针p1大于指针p2时,需要从前向后拷贝。而当两个内存区域没有重叠时,从前向后和从后向前都是可行的。

        那么,我们就可以在拷贝之前先比较一下指针dest和指针sour的大小,然后再选择是从前向后拷贝还是从后向前拷贝。

#include <stdio.h>#include <assert.h>void* my_memmove(void* dest, const void* sour, size_t count){void* pd = dest;assert(dest && sour);if (dest < sour)//从前向后{while (count--){*(char*)dest = *(char*)sour;((char*)dest)++;((char*)sour)++;}}else//从后向前{while (count--){*((char*)dest + count) = *((char*)sour + count);}}return pd;}void text1(){int i = 0;int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr1) / sizeof(arr1[0]);my_memmove(arr1 + 2, arr1, 20);for (i = 0; i < sz; i++){printf("%d ", arr1[i]);}}void text2(){int i = 0;int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr1) / sizeof(arr1[0]);my_memmove(arr1, arr1 + 2, 20);for (i = 0; i < sz; i++){printf("%d ", arr1[i]);}}void text3(){int i = 0;char str1[] = "abcdefghijklmn";int sz = sizeof(str1) / sizeof(str1[0]);my_memmove(str1, str1 + 2, 5);printf("%s\n", str1);}void text4(){int i = 0;char str1[] = "abcdefghijklmn";int sz = sizeof(str1) / sizeof(str1[0]);my_memmove(str1 + 2, str1, 5);printf("%s\n", str1);}int main(){//text1();//text2();//text3():text4();return 0;}

        这时候我们写的 my_memmove 函数就比较完善了。

        其实小伙伴们也能感觉到 memmove 函数完全可以代替 memcpy 函数,而且 memmove 函数不用管内存是否重叠的问题。那 memcpy 函数不就没有存在的必要了吗?其实内存重叠只是一种特殊情况,在确定没有内存重叠的情况下使用 memcpy 函数效率会更高,因为 memcpy 函数没有比较指针大小这一步骤。

        当然如果你嫌麻烦始终使用 memmove 函数也是没有什么问题的,就是效率低那么一丢丢而已。

3、memset 函数的使用

        memset 函数是用来设置内存的,它的作用是将内存中的值以字节为单位设置成想要的内容。 

        需要注意的是,memset 函数以字节为单位设置的,如果对这个规则模糊的话很容易写出下面这种代码:

        我们知道整型占4个字节,整数7以16进制表示为:0x07 00 00 00,上面的代码执行过后就变成了:0x01 01 01 01,并没有达到我们想要的效果。所以我们要谨记 memset 函数是以字节为单位一个字节一个字节设置的,并不是以元素为单位的。

4、memcmp 函数的使用

        memcmp 函数和 strncmp 函数极其相似,也是比较两个指针指向内容的大小,唯一的区别是 strncmp 只能比较字符串,而 memcmp 可以比较任意类型。和 memset 函数一样 memcmp 也是以字节为单位比较的。 

  

        以上所有的函数都是可以操作内存的函数,与前面介绍的字符、字符串函数不同的是内存函数可以操作任意类型的内容。 

          如果觉得我的文章还不错,请点赞、收藏 + 关注支持一下,我会持续更新更好的文章。  

                                                点击跳转下一节 —> C语言(数据存储) 



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。