【C语言】内存函数与数据在内存中的存储

发布时间:2026/7/14 14:58:12
【C语言】内存函数与数据在内存中的存储 文章目录一、 内存函数1. memcpy内存复制函数函数特点使用示例模拟实现2. memmove处理重叠内存的复制函数函数特点使用示例模拟实现3. memset内存设置函数函数特点使用示例4. memcmp内存比较函数函数特点使用示例总结二、 数据在内存中的存储1. 整数在内存中的存储1.1 原码、反码、补码的定义1.2 为什么使用补码2. 大小端字节序和字节序判断2.1 大小端的定义2.2 为什么存在大小端2.3 如何判断机器的字节序方法1指针法方法2联合体法3. 浮点数在内存中的存储3.1 IEEE 754标准格式3.2 存储结构3.3 存储与读取规则存储过程读取过程3.4 示例解析4. 经典练习解析练习1字符类型的符号影响练习2循环陷阱练习3练习4在C语言中内存操作是编程的核心环节之一。四个常用的内存函数memcpy、memmove、memset和memcmp一、 内存函数1. memcpy内存复制函数memcpy函数用于从源内存地址向目标内存地址复制指定字节数的数据其函数原型void*memcpy(void*destination,constvoid*source,size_tnum);函数特点从source指向的内存位置开始复制num个字节到destination指向的内存位置。返回目标空间起始地址不会因遇到\0而停止复制严格按照指定的字节数操作。不处理重叠内存如果源地址和目标地址的内存区域有重叠复制结果是未定义的。使用示例#includestdio.h#includestring.hintmain(){intarr1[]{1,2,3,4,5,6,7,8,9,10};intarr2[10]{0};// 复制20个字节即5个int元素每个int占4字节memcpy(arr2,arr1,20);for(inti0;i10;i){printf(%d ,arr2[i]);// 输出1 2 3 4 5 0 0 0 0 0}return0;}模拟实现#includeassert.hchar*my_memcpy(void*dest,void*src,size_tnum){assert(destsrc);char*ret(char*)dest;while(num--){*(char*)dest*(char*)src;(char*)dest(char*)dest1;(char*)src(char*)src1;}returnret;}注意通过将指针强制转换为char*每次访问1字节实现按字节操作确保对任意类型的内存都能正确复制。循环内部如果想写成自增要这样写((char*)dest);2. memmove处理重叠内存的复制函数memmove与memcpy功能类似但它支持源内存和目标内存重叠的场景函数原型void*memmove(void*destination,constvoid*source,size_tnum);函数特点与memcpy的核心区别可以安全处理重叠的内存区域。当源地址和目标地址重叠时使用memmove可保证复制结果正确。使用示例#includestdio.h#includestring.hintmain(){intarr1[]{1,2,3,4,5,6,7,8,9,10};// 将arr1的前5个元素20字节复制到arr12的位置从第3个元素开始memmove(arr12,arr1,20);for(inti0;i10;i){printf(%d ,arr1[i]);// 输出1 1 2 3 4 5 6 7 8 9 10原文档此处代码笔误arr2应为arr1}return0;}模拟实现#includeassert.hchar*my_memmove(void*dest,void*src,size_tnum){assert(destsrc);char*retdest;if(destsrc)//从前向后{while(num--){*(char*)dest*(char*)src;((char*)dest);((char*)src);}}if(destsrc)//从后向前{while(num--){*((char*)destnum)*((char*)srcnum);}}returnret;}intmain(){intarr[20]{1,2,3,4,5,6,7,8,9,10};char*retmy_memmove(arr2,arr,20);return0;}注意dest地址小于src时应该正向复制dest地址大于src时应逆向赋值避免覆盖未复制的数据3. memset内存设置函数memset用于将指定内存区域的每个字节设置为指定值函数原型如下void*memset(void*ptr,intvalue,size_tnum);函数特点以字节为单位设置内存值value会被转换为unsigned char后填充。常用于初始化数组或清空内存区域。使用示例#includestdio.h#includestring.hintmain(){charstr[]hello world;// 将前6个字节设置为xmemset(str,x,6);printf(str);// 输出xxxxxxworldreturn0;}注意memset按字节操作若用于设置int数组可能无法达到预期效果如memset(arr, 1, 4)会将int值设为0x01010101而不是1。4. memcmp内存比较函数memcmp用于比较两个内存区域的前num个字节函数原型intmemcmp(constvoid*ptr1,constvoid*ptr2,size_tnum);函数特点按字节比较每个字节视为unsigned char类型。返回值表示两个内存区域的大小关系0ptr1的第一个不同字节小于ptr2的对应字节0两个内存区域的前num个字节完全相同0ptr1的第一个不同字节大于ptr2的对应字节。使用示例#includestdio.h#includestring.hintmain(){charbuffer1[]DWgaOtP12df0;charbuffer2[]DWGAOTP12DF0;intnmemcmp(buffer1,buffer2,sizeof(buffer1));if(n0)printf(%s is greater than %s\n,buffer1,buffer2);elseif(n0)printf(%s is less than %s\n,buffer1,buffer2);elseprintf(%s is the same as %s\n,buffer1,buffer2);return0;}//输出DWgaOtP12df0 is greater than DWGAOTP12DF0说明示例中因小写字母的ASCII值大于大写字母buffer1会被判定为大于buffer2。总结函数功能特点适用场景memcpy复制内存不处理重叠内存效率较高非重叠内存的复制memmove复制内存处理重叠内存安全性高可能重叠的内存复制memset设置内存值按字节操作用于初始化或填充内存初始化、批量设置值memcmp比较内存按字节比较返回差值关系任意类型内存的比较二、 数据在内存中的存储1. 整数在内存中的存储整数在内存中以补码形式存储这是由计算机系统的特性决定的。要理解补码需先掌握原码、反码的概念1.1 原码、反码、补码的定义符号位最高位为符号位0表示正数1表示负数。数值位剩余位表示数值大小。正整数原码、反码、补码完全相同。示例int a 532位原码00000000 00000000 00000000 00000101反码00000000 00000000 00000000 00000101补码00000000 00000000 00000000 00000101负整数原码直接翻译二进制符号位为1。反码符号位不变数值位按位取反。补码反码 1。示例int b -532位原码10000000 00000000 00000000 00000101反码11111111 11111111 11111111 11111010补码11111111 11111111 11111111 111110111.2 为什么使用补码统一符号位和数值位补码让符号位参与运算无需额外处理。简化运算CPU只有加法器补码可将减法转换为加法如a - b a (-b)。节省硬件补码与原码的转换规则统一无需额外电路。2. 大小端字节序和字节序判断存储0x11223344这样一个数据我们调试打开内存发现整数在内存中存储的是二进制补码调试窗口为了方便展示转化为16进制的值存储的顺序是倒过来的当数据占多个字节时会涉及字节在内存中的排列顺序即字节序。2.1 大小端的定义大端字节序数据的高位字节存于内存低地址低位字节存于高地址。示例0x11223344存储为11 22 33 44低地址到高地址。小端字节序数据的低位字节存于内存低地址高位字节存于高地址。示例0x11223344存储为44 33 22 11低地址到高地址。2.2 为什么存在大小端计算机以字节为内存单位对于超过1字节的数据如short、int不同硬件架构对字节排列顺序有不同约定X86、ARM默认等架构采用小端。KEIL C51、部分ARM配置采用大端。2.3 如何判断机器的字节序通过代码可快速判断当前系统的字节序方法1指针法#includestdio.hintcheck_sys(){inti1;// 取i的地址并强制转换为char*仅访问第一个字节return*(char*)i;}intmain(){if(check_sys()1){printf(小端\n);// 小端中第一个字节为0x01}else{printf(大端\n);// 大端中第一个字节为0x00}return0;}方法2联合体法#includestdio.hintcheck_sys(){union{inti;charc;}un;un.i1;returnun.c;// 联合体成员共用内存c访问第一个字节}3. 浮点数在内存中的存储举个例子#includestdio.hintmain(){intn9;float*pFloat(float*)n;printf(n的值为%d\n,n);printf(*pFloat的值为%f\n,*pFloat);*pFloat9.0;printf(num的值为%d\n,n);printf(*pFloat的值为%f\n,*pFloat);return0;}运行结果以浮点型指针访问整数得到的结果不是9以整型打印浮点数得到的结果不是9.0由此说明浮点数和整数在内存中的存储不同浮点数float、double的存储方式与整数完全不同遵循IEEE 754标准。3.1 IEEE 754标准格式任意二进制浮点数V可表示为[ V (-1)^S * M * 2^E ]S符号位0为正1为负。M有效数字1 ≤ M 2。E指数位整数。3.2 存储结构32位float1位S 8位E 23位M。64位double1位S 11位E 52位M。3.3 存储与读取规则存储过程规范化MM默认省略整数位1仅存小数部分如1.001存为001节省1位空间。调整EE为无符号整数存储时需加上中间值float加127double加1023。示例E3float→ 存储为3127130二进制10000010。读取过程E不全为0且不全为1E真实值 存储值 - 中间值M前补1。E全为0E真实值 1 - 中间值M前补0表示接近0的小数。E全为1M全0表示±无穷大M非0表示NaN非数值。3.4 示例解析#includestdio.hintmain(){intn9;float*pFloat(float*)n;printf(n %d\n,n);// 输出9printf(*pFloat %f\n,*pFloat);// 输出0.000000*pFloat9.0;printf(n %d\n,n);// 输出1091567616printf(*pFloat %f\n,*pFloat);// 输出9.000000return0;}第一步整数9的补码为00000000 00000000 00000000 00001001按float解读时E全为0结果接近0。第二步9.0的float存储为0 10000010 00100000000000000000000按整数解读时为1091567616。4. 经典练习解析练习1字符类型的符号影响#includestdio.hintmain(){chara-1;signedcharb-1;unsignedcharc-1;printf(a%d, b%d, c%d,a,b,c);// 输出a-1, b-1, c255return0;}char和signed char-1的补码为11111111打印时符号扩展为11111111 11111111 11111111 11111111仍为-1。unsigned char-1的补码为11111111无符号解读为255。char是有符号signed char还是无符号unsigned char取决于编译器在VS2022上是有符号的%d打印有符号整数把最高位当做符号位%u%zd打印无符号整数把最高位当做有效位练习2循环陷阱#includestdio.hunsignedchari0;intmain(){for(i0;i255;i){printf(hello world\n);// 无限循环}return0;}unsigned char范围为0~255i后始终≤255循环永不结束。练习3intmain(){char[1000];for(inti0;i1000;i{a[i]-1-i;}//-1 -2 -3 -4 ... -128 127 126 125 ....4 3 2 1 0printf(%zd ,,strlen(a);//255return0;}-128-1的值是127继续减到0strlen统计\0前的字符个数字符个数是128127255练习4x86环境小端字节序#includestdio.hintmain(){inta[4]{1,2,3,4};int*ptr1(int*)(a1);int*ptr2(int*)((int)a1);printf(%x,%x,ptr1[-1],*ptr2);//4 2 00 00 00return0;}指针1取决于指针的类型整数1 直接1%x用于打印十六进制的整数a取出整个数组的地址1指向数组后一个地址ptr1[-1] *(ptr1-1)向前跳过一个字节指向4将a转化为整型再1向后跳过一个字节在x86小端字节序的环境下ptr2指向00 00 00 02再转化为02 00 00 00