【C语言】——内存函数的使用及模拟实现

慈云数据 2024-05-30 技术支持 39 0

C语言】——内存函数的使用及模拟实现

    • 前言
    • 一、 m e m c p y memcpy memcpy 函数
      • 1.1、函数功能
        • (1)函数名理解
        • (2)函数介绍
        • 1.2、函数的使用
        • 1.3、函数的模拟实现
        • 二、 m e m m o v e memmove memmove 函数
          • 2.1、函数功能
          • 2.2、函数的使用
          • 2.3、函数的模拟实现
            • (1)思路一
            • (2)思路二
            • (3)代码实现
            • 三、 m e m s e t memset memset 函数
              • 3.1、函数功能
              • 3.2、函数的使用
              • 3.3、函数的模拟实现
              • 四、 m e m c m p memcmp memcmp 函数
                • 4.1、函数功能
                • 4.2、函数的使用
                • 4.3、函数的模拟实现

                  前言

                    在【C语言】——字符串函数的使用与模拟实现(上)与【C语言】——字符串函数的使用与模拟实现(下)二文中,我曾详细介绍了字符串相关函数的具体功能及其模拟实现。

                    

                    然而,实践过程中,我们往往不仅仅只是处理字符串类型的变量,若要对浮点型、整型、结构体等其他类型的数据进行相关操作,又该怎么办呢?

                    

                    这时,就需要用到的内存操作函数了

                    

                    接下来,让我们一起来学习学习吧

                    

                    

                  一、 m e m c p y memcpy memcpy 函数

                  1.1、函数功能

                  (1)函数名理解

                    

                  在介绍 m e m c p y memcpy memcpy函数 的功能前,我们先来对函数名进行分析

                  • m e m mem mem 表示的是 m e m o r y memory memory,它有记忆、内存的意思,这里它表示的是内存
                  • c p y cpy cpy 即 c o p y copy copy,表示拷贝

                      这样我们从名字就能大体知道这个函数的大致功能啦,像之前的 s t r c p y strcpy strcpy函数,它是实现对字符串的拷贝,而 m e m c p y memcpy memcpy函数,实现的是对内存块的拷贝。

                      

                      

                    (2)函数介绍

                    在这里插入图片描述

                    功能:将源字符串的前 n u m num num 个内存块内容拷贝到目标字符串中

                    • 函数 m e m c p y memcpy memcpy 从 s o u r c e source source 的位置开始向后复制 n u m num num 个字节的数据到 d e s t i n a t i o n destination destination 指向的内存为止
                    • 函数的使用需包含头文件
                    • 因为拷贝的数据不一定是字符串,函数在遇到 ‘\0’ 时不会停下来
                    • 如果 s o u r c e source source 和 d e s t i n a t i o n destination destination 有任何的重叠,复制的结果都是未定义的
                    • d e s t i n a t i o n destination destination 所指向的空间必须足够大,至少 n u m num num 个字节大小,以避免溢出
                    • 返回值是目标空间的地址

                      注:对于重叠的内存,交给 m e m m o v e memmove memmove函数 来处理。

                        

                      1.2、函数的使用

                        

                      例如:我们将整型数组 a r r 1 arr1 arr1 的前 5 个元素复制给 a r r 2 arr2 arr2 数组

                        

                      代码如下:

                      #include
                      #include
                      int main()
                      {
                      	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
                      	int arr2[20] = { 0 };
                      	memcpy(arr2, arr1, 20);
                      	int i = 0;
                      	for (i = 0; i  
                      

                        

                      运行结果:

                      在这里插入图片描述

                        

                        可能有些小伙伴在调用 m e m c p y memcpy memcpy 函数时, n u m num num 的传参会写成 5 :memcpy(arr2, arr1, 5);,这里需要注意: m e m c p y memcpy memcpy 拷贝是内存块为单位,即字节,而不是要拷贝的元素的个数。

                        当然, n u m num num 的传参也可以写成 元素个数 ∗ * ∗ s i z e o f sizeof sizeof(类型)

                        如:上述代码调用 m e m c p y memcpy memcpy 函数可写成:memcpy(arr2, arr1, 5*sizeof(int));

                        

                        那如果用 m e m c p y memcpy memcpy 拷贝重叠空间会怎样呢?我们用自己写的模拟函数试一下

                      int main()
                      {
                      	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
                      	my_memcpy(arr1 + 2, arr1, 20);
                      	int i = 0;
                      	for (i = 0; i  
                      

                        

                      运行结果:

                      在这里插入图片描述

                        

                      为什么会这样呢?具体原因稍后解释。

                        

                        

                      1.3、函数的模拟实现

                        

                         m e m c p y memcpy memcpy函数 的实现思路和 s t r c p y strcpy strcpy 的实现差不多,即逐一拷贝每个内存块(详情请看:【C语言】——字符串函数的使用与模拟实现(上))

                        

                      void* my_memcpy(void* dest, void* src, size_t num)
                      {
                      	void* ret = dest;
                      	assert(dest && src);
                      	while (num--)
                      	{
                      		*(char*)dest = *(char*)src;
                      		dest = (char*)dest + 1;
                      		src = (char*)src + 1;
                      	}
                      	return ret;
                      }
                      

                        

                      代码逐行解析:

                      • 参数用 v o i d void void* 的原因: m e m c p y memcpy memcpy 拷贝的是任意类型的数据,而函数事先并不知道你所用传递的数据的类型,因此用 v o i d void void * 指针,以便能接收所有类型的数据。
                      • assert(dest && src);断言,后面要对指针进行解引用,因此不能传入空指针。
                      • while (num--)循环条件的判断, n u m num num 不为 0,即为真,进入循环,后 n u m num num 自减
                      • *(char*)dest = *(char*)src;【C语言】—— 指针一 : 初识指针(上)一文中,曾提到不能对 v o i d void void * 指针进行解引用操作,因此要改变其所指向的值,要对其进行强制类型转换。因为 c h a r char char * 类型一次的访问权限只有一个字节,因此将其强转成 c h a r char char* 类型,再进行解引用、赋值
                      • dest = (char*)dest + 1;指针向后移,指向下一个字节

                          

                          这里有小伙伴可能会问:dest = (char*)dest + 1;我觉得有点啰嗦,能不能写成(char*)dest++;呢?

                          答案是:不能

                          为什么呢?

                          因为强制类型转换是临时的。这里的 ++,不是对 c h a r char char* 类型的 d e s t dest dest++,而是对 v o i d void void* 类型的 d e s t dest dest++

                          上述代码可拆分成两句:(char*)dest;,dest++;

                          

                          那((char*)dest)++;可行吗?

                          基本是没问题的,但需要注意的是,有部分编译器可能编不过去。

                          

                          因此,循环语句的内容也可写成:

                        	while (num--)
                        	{
                        		*((char*)dest)++ = *((char*)src)++;
                        	}
                        

                        当然,最好还是上面的那种写法。

                          

                          

                        二、 m e m m o v e memmove memmove 函数

                        2.1、函数功能

                          

                          在前面将 m e m c p y memcpy memcpy函数 时,我曾提到,对于重叠的内存,交给 m e m m o v e memmove memmove函数 来处理,那么 m o m m o v e mommove mommove函数 又是做什么的呢?

                          

                        在这里插入图片描述

                        功能:将源字符串的前 n u m num num 个内存块内容拷贝到目标字符串中

                        • 和 m e m c p y memcpy memcpy函数 的差别就是 m e m m o v e memmove memmove函数 处理的源内存块和目标内存块可以重叠的。
                        • 如果源空间和目标空间出现重叠,就得使用 m e m m o v e memmove memmove函数 处理。

                            

                          2.2、函数的使用

                          #include
                          #include
                          int main()
                          {
                          	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
                          	memcpy(arr + 2, arr, 20);
                          	int i = 0;
                          	for (i = 0; i  
                          

                            

                          运行结果:

                          在这里插入图片描述

                            

                          图示:

                          在这里插入图片描述

                            

                            可以看到,复制 5 个元素,目标空间与源空间重合了,这时,就不能再使用 m e m c p y memcpy memcpy 函数,而要用 m e m m o v e memmove memmove 函数了

                            

                          在这里插入图片描述

                            

                            

                          2.3、函数的模拟实现

                          (1)思路一

                            

                            要实现重叠空间的拷贝,有什么办法呢?

                            我们想到,可以另外开辟一个数组,将原数组所有数据进行拷贝。然后,再对需要拷贝的元素进行拷贝。

                          在这里插入图片描述

                            但是,这个方法太过繁琐,而且要单独创建数组,浪费空间,这里我们暂不考虑。

                            

                          (2)思路二

                            那还有什么办法呢?我们不妨想一想,为什么内存空间重叠不能用memcpy函数?因为新拷贝的内容会将原来真正想拷贝的内容覆盖,这样最终拷贝出来的结果就不是自己想要的

                            

                          图示:

                          在这里插入图片描述

                            那有什么办法可以让先拷贝的内容不覆盖后拷贝的内容呢?

                            我们可以从后往前拷贝,如图:

                            在这里插入图片描述

                            

                            这时,有小伙伴就说了:只要以后我们都从后往前拷就行了

                            但问题又来了,如果是从 a r r arr arr + 2 开始拷贝 5 个元素到 a r r arr arr 呢?

                            

                          如图:

                          在这里插入图片描述

                            

                            发现又出问题了,但问题和之前一样,先拷贝的内容覆盖了原数据,导致后拷贝的内容错误

                            

                            这时就需要用到从前往后拷贝了

                            在这里插入图片描述

                            

                            那什么时候从后往前拷,什么时候从前往后拷呢?

                            

                          结论:

                          • 当两块空间重叠时,目标空间在源空间前面,从前向后拷贝
                          • 当两块空间重叠时,目标空间在源空间后面,从后向前拷贝
                          • 当两块空间不重叠时,两种拷贝方法都行

                              

                            图示:

                            在这里插入图片描述

                              

                              

                            (3)代码实现

                              

                              这里,空间不重叠的部分我们用从后向前,因为这样只用在 s r c src src 处分界即可

                            void* my_memmove(void* dest, void* src, size_t num)
                            {
                            	void* ret = dest;
                            	assert(dest && src);
                            	if (src > dest)
                            	{
                            		while (num--)
                            		{
                            			//前->后
                            			*(char*)dest = *(char*)src;
                            			dest = (char*)dest + 1;
                            			src = (char*)src + 1;
                            		}
                            	}
                            	else
                            	{
                            		//后->前
                            		while (num--)
                            		{
                            			*((char*)dest + num) = *((char*)src + num);
                            		}
                            	}
                            	return 0;
                            }
                            

                              

                              这段代码从前往后的情况与 m e m c p y memcpy memcpy 情况相同,就不再过多解释,来看从后往前的情况

                            • while (num--)这里循环判断条件与上面一样,当 n u m num num 大于 0,进入循环,随后进行自减。
                            • *((char*)dest + num) = *((char*)src + num);这里我们发现,如果要拷贝 20 字节内容,即 n u m num num 等于 20,那么起始位置加 n u m num num - 1 所指向的就是最后一个要拷贝的字节,而 n u m num num 在进入循环后已经自减,因此这里直接加 n u m num num 。再接着,再次循环, n u m num num 再次自减,拷贝倒数第二个字节内容,以此类推,直至 n u m num num 为 0 拷贝结束。

                                

                                

                              三、 m e m s e t memset memset 函数

                              3.1、函数功能

                              在这里插入图片描述

                              功能:设置内存,将内存中的值以字节为单位设置成想要的内容

                              • p t r ptr ptr:指向要设置的内存块
                              • v a l u e value value:要设置的值
                              • n u m num num:要设置字节的个数
                              • 返回值:要设置的内存,即 p t r ptr ptr

                                  

                                3.2、函数的使用

                                #include
                                #include
                                int main()
                                {
                                	char str[] = "Hello World";
                                	memset(str, 'x', 6);
                                	printf(str);
                                	printf("\n);
                                	return 0;
                                }
                                

                                  

                                运行结果:

                                在这里插入图片描述

                                  

                                  这时,有小伙伴问能不能用它来设置整型呢?当然可以

                                #include
                                #include
                                int main()
                                {
                                	int arr[10];
                                	memset(arr, 0, 10*sizeof(int));
                                	int i = 0;
                                	for (i = 0; i  
                                

                                  

                                运行结果:

                                在这里插入图片描述

                                  

                                  那又有小伙伴问全部设置成 1 可以吗?我们来试试

                                #include
                                #include
                                int main()
                                {
                                	int arr[10];
                                	memset(arr, 1, 10*sizeof(int));
                                	int i = 0;
                                	for (i = 0; i  
                                

                                  

                                运行结果:

                                在这里插入图片描述

                                  为什么会这样?

                                  我们要注意: m e m s e t memset memset 是以字节为单位来设置的。

                                  1 的二进制是 00000000 00000000 00000000 00000001

                                  而 m e m s e t memset memset 是将每个字节单独设置成 1,即一个整型被设置成:00000001 00000001 00000001 00000001,转换成二进制即:16843009

                                  

                                3.3、函数的模拟实现

                                void* memset(void* ptr, int value, size_t num)
                                {
                                	assert(ptr);
                                	while (num--)
                                	{
                                		*((char*)ptr + num) = value;
                                	}
                                	
                                	return ptr;
                                }
                                

                                  

                                  

                                四、 m e m c m p memcmp memcmp 函数

                                4.1、函数功能

                                在这里插入图片描述

                                功能:比较从 p t r 1 ptr1 ptr1 和 p t r 2 ptr2 ptr2 指针指向的位置开始,向后的 n u m num num 个字节

                                  

                                返回值如下:

                                在这里插入图片描述

                                • 如果返回值
                                • 如果返回值 > 0,则表示 str1 大于 str2。
                                • 如果返回值 = 0,则表示 str1 等于 str2。

                                    

                                  4.2、函数的使用

                                  int main()
                                  {
                                  	char buffer1[] = "DWgaOtP12df0";
                                  	char buffer2[] = "DWGAOTP12DF0";
                                  	int n = 0;
                                  	n = memcmp(buffer1, buffer2, sizeof(buffer1));
                                  	if (n > 0)
                                  		printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
                                  	else if(n  
                                  

                                    

                                  运行结果:

                                  在这里插入图片描述

                                    

                                  4.3、函数的模拟实现

                                  int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
                                  {
                                  	assert(ptr1 && ptr2);
                                  	while (*(char*)ptr1 == *(char*)ptr2 && num--)
                                  	{
                                  		ptr1 = (char*)ptr1 + 1;
                                  		ptr2 = (char*)ptr2 + 1;
                                  	}
                                  	return *(char*)ptr1 - *(char*)ptr2;
                                  }
                                  
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon