ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C 语言常见内存操作函数: memcpy、memmove、memset、memcmp

C 语言常见内存操作函数: memcpy、memmove、memset、memcmp 文章目录1.memcpy复制不重叠的内存区域1.1 基本用法1.2 重叠区域不使用 memcpy1.3 模拟实现2. memmove允许重叠的内存移动2.1 基本用法2.2 拷贝覆盖数组中的一段元素2.3 对于重叠部分内存的拷贝处理2.4 模拟实现3. memset用一个字节值填充 n 个字节3.1 基本用法3.2 memset 的 value 是字节不是 int 数组元素3.3 模拟实现4. memcmp比较前 n 个字节4.1 基本用法4.2 与 strcmp 的区别4.3 模拟实现5. 函数选择速查表6. 常见错误与修正习惯6.1 把元素数量当成字节数量6.2 目标空间不足6.3 误用返回值7. 总结1.memcpy复制不重叠的内存区域1.1 基本用法void*memcpy(void*destination,constvoid*source,size_tnum);参数说明destination指向目标数组的指针待复制内容将存放于此该指针被强制转换为void*类型。source指向数据源的指针待复制的数据来源该指针被强制转换为const void*类型。num需要复制的字节数量。#includestdio.h#includestring.hintmain(){intsrc[]{10,20,30,40,50};intdst[5]{0};memcpy(dst,src,sizeof(src));for(inti0;i5;i){printf(%d ,dst[i]);}return0;}运行结果sizeof(src)得到整个数组的字节数所以这里会复制 5 个 int。若只想复制前两个元素应写 2 * sizeof src[0]不能只写 22 表示两个字节而不是两个整数。memcpy 返回 dst 的首地址因此可以把返回值传给其他函数但通常直接使用目标数组更容易读懂。1.2 重叠区域不使用 memcpychartext[8]abcdef;memcpy(text1,text,5);// 源和目标重叠行为未定义memcpy 的前提是源区域和目标区域完全不重叠。不确定是否重叠时使用 memmove。1.3 模拟实现void*my_memcpy(void*des,constvoid*src,size_tcount){void*resdes;if(desNULL||srcNULL){return;}while(count--){*(char*)des*(char*)src;des(char*)des1;src(char*)src1;}return(res);}效果验证intmain(){intarr1[]{1,2,3,4,5,6,7,8,9,10};intarr2[10]{0};my_memcpy(arr2,arr1,20);//将arr1中20字节5个int元素内容拷贝到arr2for(inti0;i10;i){printf(%d ,arr2[i]);}return0;}运行结果这个模拟版本仍要求区域不重叠、地址有效且目标容量至少为 n 字节2. memmove允许重叠的内存移动2.1 基本用法void*memmove(void*destination,constvoid*source,size_tnum);参数说明destination指向目标数组的指针待复制内容将存放于此指针会被强制转换为void*类型。source指向数据源的指针待复制的数据来源指针会被强制转换为const void*类型。num需要复制的字节数量。#includestdio.h#includestring.hintmain(){chartext[16]abcdef;memmove(text1,text,strlen(text)1);printf(%s\n,text);return0;}运行结果2.2 拷贝覆盖数组中的一段元素intvalues[]{10,20,30,40,50};size_tcount5;size_tremove_at1;size_tremove_count2;memmove(values[remove_at],values[remove_atremove_count],(count-remove_at-remove_count)*sizeofvalues[0]);count-remove_count;移动后数组前 3 个有效元素是10, 40, 50。尾部的两个位置仍然保留旧值但它们已经不属于有效范围如果数据信息不用了的话可以再用 memset 清理。memmove 只负责搬运不会自动更新 count。2.3 对于重叠部分内存的拷贝处理memmove内部会判断源地址和目标地址的位置自动选择正向拷贝或者反向拷贝所以可以安全处理内存重叠。对于内存重叠举例如下数组{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } src buf[2], dest buf[0], n 4 也就是要把 3,4,5,6,7 复制到 1,2,3,4,5 或者反过来 src buf[0], dest buf[2], n 4 把 1,2,3,4,5 复制到 3,4,5,6,7当使用memcpy 的实现思路时会对于源地址以及目标地址进行从头到尾依次拷贝覆盖。问题如果dest在src的前面并且区域重叠比如srcbuf[0], destbuf[2], n4原始数据0,1,2,3,4,5第一步d[0]s[0]→ buf [2]1第二步d[1]s[1]→ buf [3]2第三步d[2]s[2]→s [2] 已经被前面覆盖成 1 了读到的不是原来的 3而是已经被改写的值 → 结果出错如下所示。而memmove的实现会先判断地址大小目标在源前面正向拷贝和memcpy一样目标在源后面且重叠反向拷贝从最后一个字节往前复制以srcbuf[0], destbuf[2], n4为例我们先复制最后一个字节再往前先拷贝 s [3] → d [3]再 s [2]→d [2]……不会提前覆盖还没读取的源数据完美解决重叠问题。2.4 模拟实现void*my_memmove(void*des,constvoid*src,size_tcount){void*resdes;if(desNULL||srcNULL){return;}//des起点在src前//正常从前往后复制if(dessrc){while(count--){*(char*)des*(char*)src;des(char*)des1;src(char*)src1;}}//从后往前复制else{des(char*)descount-1;src(char*)srccount-1;while(count--){*(char*)des*(char*)src;des(char*)des-1;src(char*)src-1;}}return(res);}效果验证intmain(){intarr1[]{1,2,3,4,5,6,7,8,9,10};//将arr1首元素后20字节5个int元素拷贝到 arr1 2地址处my_memmove(arr12,arr1,20);for(inti0;i10;i){printf(%d ,arr1[i]);}return0;}运行结果3. memset用一个字节值填充 n 个字节3.1 基本用法void*memset(void*ptr,intvalue,size_tnum);参数说明ptr指向待填充内存块的指针。value要设置的值。该值以int类型传入但函数会将该值转换为unsigned char类型后填充内存块。num需要设置为该值的字节数量。charbuffer[32];memset(buffer,0,sizeofbuffer);memset用于清零最为常见这会把 32 个字节都写成 0常用于初始化字符缓冲区或清空一块原始存储。3.2 memset 的 value 是字节不是 int 数组元素inta[4];memset(a,1,sizeof(a));这里每个字节都被写成 0x01。在常见机器上每个 int 可能变成 0x01010101十进制16843009而不是 1。value的类型虽然是int但函数只取它转换后的unsigned char低 8 位来重复填充。例如memset(buf, 0x123, n)实际填入的通常是0x23代码应避免依赖超出unsigned char范围的值。3.3 模拟实现void*my_memset(void*ptr,intvalue,size_tn){unsignedchar*p(unsignedchar*)ptr;unsignedcharbyte(unsignedchar)value;size_ti;for(i0;in;i){p[i]byte;}returnptr;}效果验证intmain(){inta[5];//将a数组内5个元素全设置为0my_memset(a,0,5*sizeof(a[0]));for(inti0;i5;i){printf(%d ,a[i]);}return0;}运行结果4. memcmp比较前 n 个字节4.1 基本用法intmemcmp(constvoid*ptr1,constvoid*ptr2,size_tnum);参数说明ptr1指向一块内存的指针。ptr2指向另一块内存的指针。num需要比较的字节数量。返回值含义0两块内存中第一个不匹配的字节按unsigned char解读后ptr1该字节的值小于ptr2对应字节0两块内存指定长度内的内容完全相等0两块内存中第一个不匹配的字节按unsigned char解读后ptr1该字节的值大于ptr2对应字节unsignedchara[]{1,2,3};unsignedcharb[]{1,2,4};if(memcmp(a,b,sizeof(a))0){printf(完全相同\n);}else{printf(存在差异\n);}memcmp 按 unsigned char 逐字节比较遇到第一处不同就停止返回值小于 0、等于 0 或大于 0分别表示第一处不同字节的大小关系。标准不保证非零返回值一定是 -1 或 1不要写 memcmp(…) 1 来判断不相等。4.2 与 strcmp 的区别charraw[]{a,\0,b};charraw2[]{a,\0,c};printf(%d\n,memcmp(raw,raw2,sizeof(raw)!0);// 1 不相同printf(%d\n,strcmp(raw,raw2)0);// 1 相同strcmp 在第一个\0处就结束所以它把两段数据都看成amemcmp则按指定的 3 个字节比较能发现最后一个字节不同。处理二进制数据或固定长度字段时memcmp才是合适方向。4.3 模拟实现intmy_memcmp(constvoid*lhs,constvoid*rhs,size_tn){constunsignedchar*a(constunsignedchar*)lhs;constunsignedchar*b(constunsignedchar*)rhs;size_ti;for(i0;in;i){if(a[i]!b[i]){return(a[i]b[i])?1:-1;}}return0;}效果验证charstr1[]abcde;charstr2[]abcdef;if(!my_memcmp(str1,str2,sizeof(str2))){printf(相同\n);}else{printf(存在差异\n);}运行结果5. 函数选择速查表需求函数是否寻找 ‘\0’是否允许源/目标重叠复制 n 个字节memcpy否否移动 n 个字节memmove否是重复填充 n 个字节memset否不适用比较 n 个字节memcmp否不修改输入复制字符串strcpy是否6. 常见错误与修正习惯6.1 把元素数量当成字节数量对于int values[10]复制 10 个元素应写10 * sizeof values[0]或在源数组仍是数组时写sizeof(values)。6.2 目标空间不足memset(buf, 0, n)是逐字节清零memset(numbers, 1, sizeof(numbers))不是把每个整数设为 1(而是逐个字节设置为1)。6.3 误用返回值复制类函数返回目标地址不是复制了多少字节memcmp 返回的是符号结果不是布尔值注意使用前先看函数原型再写判断。7. 总结memcpy适合不重叠复制memmove专门处理可能重叠的移动memset重复写入一个字节memcmp比较固定长度的字节序列它们共同遵守一条规则函数只相信你传入的地址和 n不会替你猜测容量、类型或数据含义。
返回列表