C语言第十章内存函数
一. memcpy函数的使用和模拟实现
1.函数的使用
该函数和上一章节学到的strcpy比较相似,本章节是通过内存来实现数据的复制,而上章节的strcpy函数主要是通过数据本身实现复制操作。
根据上述图片可以得到,该函数的函数参数为void *类型。实现了泛型编程。可以接收不同类型的数据。函数返回值为void*类型,强制类型转换之后就可以得到目标类型的函数返回值了。
函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。这个函数在遇到 '\0' 的时候并不会停下来,因为该函数本质上使用的是内存进行数据的复制,而不是读取内容从而实现数据的复制。如果source和destination有任何的重叠,就不可以达到预期效果,复制的结果都是未定义的(具体详见模拟实现的说模拟说明)。
下面举一个该函数使用的例子:
#include <stdio.h>
#include <string.h> //该函数需要的头文件
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; //创建两个整型数组,用于接下来的内存复制int arr2[10] = { 0 };memcpy(arr2, arr1, 20); //调用函数,将从arr1开始,复制20个字节的数据给arr2int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr2[i]); //通过循环进行数组的打印,查看复制效果}return 0;
}
上述是函数的使用代码,首先创建了两个整型数组,一个是接收复制内容的目标空间;另一个是提供复制内容的源头数组。调用函数时,第三个参数表示的是复制内容的大小(单位是字节)。所以上述代码可以实现:将arr1的前20个字节的内容复制到arr2数组中去。
2.函数的模拟实现
void * memcpy ( void * dst, const void * src, size_t count)
{void * ret = dst; //保存首地址,方便后续处理返回值assert(dst); //防止接收的函数参数指针为空指针,避免影响解引用操作assert(src);while (count--) {*(char *)dst = *(char *)src; //将地址先进行强制类型转换后解引用,最后进行数据的复制dst = (char *)dst + 1;
//将指针后移,因为强制类型转换操作是暂时性的,当强转之后进行后置++,此时强制类型转换过了时效性,就会产生对 void*指针解引用的错误操作。所以采用简单的+1计算操作src = (char *)src + 1;}return(ret);
}
根据上方模拟实现函数的代码和图片解释可得,函数需要以字节为单位复制数据,这时候选择强制类型转化为char*,原因是:char*的大小为1个字节,刚好为函数参数的最小单位。这样无论碰到什么类型的数据,都会逐一字节进行复制,实现了泛型编程。函数在实现过程中,通过控制while循环的循环条件,从而控制复制内容字节数的大小。
指针后移不用简便的后置++,原因是:C语言的强制类型转化操作具有暂时性,当进行到自增运算时,强制类型转换就会过了时效性,导致代码解引用void*类型的指针,这种行为是具有危险性的,所以这里的模拟实现采用了简单的+1操作。
二.memmove函数使用和模拟实现
1.函数的使用
讲到该函数,貌似这个函数就是为了弥补memcpy函数的不足的(不能复制重叠的数据)。该函数可以将一段内存数据从源地址复制到目标地址处。
通过上述图片可得,函数的返回类型和每个参数和memcpy函数极为相似,实际上目的也是相同的。都实现了泛型编程。
memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用memmove函数进行数据内容的复制。
下面举一个该函数的使用例子:
#include <stdio.h>
#include <string.h> //该函数需要的头文件
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; //创建一个数组,尝试自己给自己复制数据(重叠)memmove(arr1+2, arr1, 20); //调用函数,将arr1的前20字节数据复制到arr1+2空间处int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr1[i]); //利用循环打印该数组,查看函数调用的效果}return 0;
}
输出:1 2 1 2 3 4 5 8 9 10
上述代码的理解:上述代码通过自己复制数据给自己,来实现重叠数据复制时,函数效果的验证。根据输出结果可以得出,该函数确实具有复制重叠数据内容的能力。
2.函数的模拟实现
void * memmove ( void * dst, const void * src, size_t count)
{void * ret = dst; //保存首地址,用于最后的函数返回if (dst <= src || (char *)dst >= ((char *)src + count))//当已拷贝数据不会对未拷贝数据进行覆盖时{while (count--) //进行正向复制{*(char *)dst = *(char *)src;dst = (char *)dst + 1;src = (char *)src + 1;}}else //否则进行反向复制{dst = (char *)dst + count - 1;src = (char *)src + count - 1;while (count--) {*(char *)dst = *(char *)src;dst = (char *)dst - 1;src = (char *)src - 1;}}return(ret);
}
该函数的实现比较复杂,需要分情况讨论。因为函数需要解决重叠空间复制数据的问题,所以根据上方图片可以看出思路(可以根据目标空间和源头数据的位置关系来决定复制的方向)。
当(dst <= src || (char *)dst >= ((char *)src + count)) 时,说明已拷贝数据不会对未拷贝数据进行覆盖,所以使用正向复制的顺序;当dst位于src和src+count中间时,说明已拷贝数据会对未拷贝数据进行覆盖,所以进行反向复制。
三.memset函数的使用
该函数的作用是:改变一段内存区域的值,将其变为我们想改变的值。
通过上述图片可以得知,该函数的函数参数为void*类型,原因同上,均为达到泛型编程的目的。第二个函数参数为改变后的值。第三个参数为要填充的字节数。最后函数返回类型为void*类型返回的是数据的首地址。
这里需要注意的是,该函数填充内容时,是以字节为单位的,填充过程中,会将每个字节都填充为所需值。系统存数据是以二进制的补码进行存储的,如果填充的是大于1个字节的类型,那么一般不会达到预期的效果。
比如:将一个空间的数据(整型值2,补码为:00000000 00000000 00000000 00000010),全部变为整型值1,这时候用此函数就会将该地址存储的内容,每个字节都变为1,数据就会变成00000001 00000001 00000001 00000001,可以明显看出得到的数据并不是整型值1,原因是整型值1的存储并不是每个字节都是1。
下面举一个该函数的使用例子:
#include <stdio.h>
#include <string.h> //该函数需要的头文件
int main ()
{char str[] = "hello world"; //定义字符数组,方便后续的函数验证memset (str,'x',6); //调用函数,将str数组的前6个字节全部变为xprintf(str); //打印字符串str,查看函数的具体效果return 0;
}
输出:xxxxxx world
四.memcmp函数的使用
该函数的作用是内存比较,用于比较两个内存区域的前num字节内容大小。
根据上方图片可以看出:前两个函数参数为void *指针(待比较的两个内存区域),旨在接收任意类型的数据,增强函数的健壮性,实现泛型编程。第三个函数参数为num,用于控制比较内容的多少(单位是字节)。
该函数和strcmp函数比较相似,但是还是有些许区别。该函数不会以 \0为终止条件,不会检查内容的结束符。该函数的返回值和strcmp相同:第一个函数参数大时,函数返回大于0 的数字;第二个函数参数大时,函数返回小于0的数字;两个函数参数一样大时,返回数字0。
下面是该函数的使用举例:
#include <stdio.h>
#include <string.h> //该函数需要的头文件
int main()
{char buffer1[] = "DWgaOtP12df0"; //创建两个字符数组,用于接下来的函数验证char buffer2[] = "DWGAOTP12DF0";int n;n = memcmp(buffer1, buffer2, sizeof(buffer1)); //调用函数比较两字符串的前sizeof(buffer1)字节内容if (n > 0) //根据判断结果打印相应的内容printf("'%s' is greater than '%s'.\n", buffer1, buffer2);else if (n < 0) printf("'%s' is less than '%s'.\n", buffer1, buffer2);else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);return 0;
}
上述代码,通过调用该函数判断,来判断两内存区域数据内容的大小,这和strcmp函数类似,均是逐个字节进行ASCII码值大小的比较,比较的并不是内容的长短。