当前位置: 首页 > news >正文

C语言自学--数据在内存中的存储

目录

 

1、整数在内存中的存储

2. 大小端字节序和字节序判断

2.1、 什么是大小端?

2.2 、大小端模式的起源

2.3 练习

2.3.1 、练习1

2.3.2、练习2

2.3.3、练习3

2.3.4 、练习4

2.3.5、练习5

3. 浮点数在内存中的存储

3.1、练习1

3.2 浮点数的存储

3.2.1 浮点数存储过程

3.2.2 浮点数的取过程


 

1、整数在内存中的存储

        关于整数的二进制表示方法,我们之前在学习操作符时已经介绍过三种形式:原码、反码和补码。这三种表示方法都具有以下共同特点:

  1. 表示结构由符号位和数值位组成
  2. 符号位统一规定:0表示"正",1表示"负"
  3. 数值位的最高位作为符号位,其余为实际数值位

具体特性如下:

  • 正整数:原码、反码和补码表示完全相同
  • 负整数:三种表示方法各有不同:
    • 原码:直接将数值按正负数形式转换为二进制
    • 反码:保持符号位不变,其余位按位取反
    • 补码:在反码基础上加1得到

在计算机系统中,整形数据以补码形式存储在内存中,主要原因如下:

  1. 补码表示法能够统一处理符号位和数值域;
  2. 使用补码可以简化CPU设计,使加减法运算都能通过加法器实现;
  3. 补码与原码之间的转换运算过程相同,无需额外的硬件电路支持。

这种设计既优化了计算效率,又降低了硬件实现的复杂度。


2. 大小端字节序和字节序判断

在掌握了整数在内存中的存储方式后,我们来深入探讨一个调试细节:

#include <stdio.h>
int main()
{int a = 0x11223344;return 0;
}

 

        在调试过程中,我们可以观察到变量a中存储的0x11223344这个数值是以字节为单位进行倒序存储的。

2.1、 什么是大小端?

        当数据超过一个字节时,在内存中的存储顺序就变得重要。根据不同的存储顺序,可分为大端字节序和小端字节序两种模式:

  • 大端存储模式:数据的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处
  • 小端存储模式:数据的低位字节存储在内存的低地址处,高位字节存储在内存的高地址处

掌握这两个概念有助于准确区分大小端模式。


2.2 、大小端模式的起源

        大小端模式的出现源于计算机系统的基本存储特性。计算机以字节(8位)为基本寻址单位,但编程语言中存在多种数据宽度(如16位的short型、32位的long型)。当处理器位宽超过8位时(如16位或32位处理器),就必须解决多字节数据的存储顺序问题,这就产生了大小端两种存储模式。

        举例说明: 假设一个16位的short型变量x,其内存地址从0x0010开始,高字节为0x11,低字节为0x22。在大端模式中,高字节0x11存储在低地址0x0010,低字节0x22存储在高地址0x0011,整体表示为0x1122。小端模式则完全相反。

典型应用:

  • X86架构采用小端模式
  • Keil C51编译器使用大端模式
  • 多数ARM和DSP处理器采用小端模式
  • 部分ARM处理器支持通过硬件配置选择大小端模式

2.3 练习

2.3.1 、练习1

题目:简述大端字节序和小端字节序的概念,并设计一个程序来判断当前机器的字节序。(10分)- 百度笔试题

解答

  1. 概念

    • 大端字节序:数据的高字节存储在低地址,低字节存储在高地址。
    • 小端字节序:数据的低字节存储在低地址,高字节存储在高地址。
  2. 判断程序(C语言实现):

    #include <stdio.h>int check_endian() {int num = 1;char *ptr = (char *)#return (*ptr == 1) ? 0 : 1; // 0表示小端,1表示大端
    }int main() {if (check_endian()) {printf("Big Endian\n");} else {printf("Little Endian\n");}return 0;
    }
    

    2.3.2、练习2

  •  #include <stdio.h>int main(){char a= -1;signed char b=-1;unsigned char c=-1;printf("a=%d,b=%d,c=%d",a,b,c);return 0;}

2.3.3、练习3

#include <stdio.h>int main(){char a = -128;printf("%u\n",a);return 0;}
#include <stdio.h>int main(){char a = 128;printf("%u\n",a);return 0;}

        这两段代码的输出结果相同,都会打印 4294967168。以下是详细分析:

        原因分析

  char 类型在大多数系统中默认是 signed char,范围是 -128127。当赋值为 128-128 时,存储的二进制值相同(10000000),但解释方式不同。

赋值 char a = 128 时,128 超出 signed char 范围,发生溢出,实际存储的是 -128(二进制补码为 10000000)。

赋值 char a = -128 时,直接存储 -128 的补码 10000000

        输出解析

  printf 使用 %u 格式化输出时,a 会被提升为 unsigned int。对于 signed char 类型:

  • a 为负数(如 -128),提升为 unsigned int 时会进行符号扩展,高位补 1-128 的补码 10000000 扩展为 11111111 11111111 11111111 10000000,即 4294967168
  • a 为正数,高位补 0,但此例中 a 实际为 -128,因此结果相同。

     关键点

  • 二进制补码存储:128-128char 中存储的二进制相同。
  • 整数提升:char 在参与表达式或格式化输出时会被提升为 int(或 unsigned int)。
  • 符号扩展:负数提升时会填充高位 1,导致最终数值巨大。

2.3.4 、练习4

#include <stdio.h>
int main()
{char a[1000];int i;for (i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d", strlen(a));return 0;
}

        字符数组赋值

   char a[1000]; 声明了一个长度为1000的字符数组。char类型在大多数系统中占用1字节(8位),取值范围为-128到127(有符号)或0到255(无符号)。此处假设为有符号类型。

循环中,a[i] = -1 - i; 对数组进行赋值:

  • i=0时,a[0] = -1
  • i=1时,a[1] = -2
  • 依此类推。

        当i=127时,a[127] = -128char的最小值)。
        当i=128时,a[128] = -129,超过了char的表示范围,发生溢出。根据补码规则,-129的二进制表示为11111111 01111111(假设16位),截取低8位为01111111,即127。
继续递增时,a[129] = -130,截断为126,依此类推,直到a[255] = -256,截断为0。

        字符串终止符

   strlen函数计算字符串长度时,从起始地址开始扫描,直到遇到第一个'\0'(ASCII值为0)为止。根据上述赋值逻辑:

  • a[255] = 0,因此strlen(a)会在a[255]处停止。
  • 前面的有效字符为a[0]a[254],共255个字符。

        输出结果

        最终输出strlen(a)的结果为255。这是因为strlen统计的是'\0'之前的字符数量。

     关键点总结

  • char类型的溢出行为导致赋值从-1递减到0。
  • strlen依赖'\0'作为终止符,因此长度为255。

2.3.5、练习5

#include <stdio.h>
int main()
{int a[4] = { 1, 2, 3, 4 };int* ptr1 = (int*)(&a + 1);int* ptr2 = (int*)((int)a + 1);printf("%#x,%#x", ptr1[-1], *ptr2);return 0;
}

代码分析

该代码涉及指针运算和数组内存布局,需要逐步拆解其行为。

数组初始化

int a[4] = { 1, 2, 3, 4 };

数组a在内存中的布局(假设小端存储,int为4字节):

地址偏移 | 字节内容(十六进制)
0x00    | 01 00 00 00  // a[0] = 1
0x04    | 02 00 00 00  // a[1] = 2
0x08    | 03 00 00 00  // a[2] = 3
0x0C    | 04 00 00 00  // a[3] = 4

指针运算解析

int *ptr1 = (int *)(&a + 1);
  • &a是数组指针,类型为int(*)[4]
  • &a + 1会跳过整个数组(16字节),指向数组末尾后的地址。
  • ptr1[-1]等价于*(ptr1 - 1),即从ptr1回退4字节,指向a[3]的地址,值为4
int *ptr2 = (int *)((int)a + 1);
  • (int)a将数组首地址转为整数值。
  • (int)a + 1使地址增加1字节(非4字节),指向a[0]的第二个字节。
  • 转换为int*后,*ptr2会从该地址读取4字节(可能引发未对齐访问,实际行为依赖平台)。

假设小端机器: 从a[0]的第二个字节开始读取4字节,会组合a[0]的后3字节和a[1]的第1字节:

原内存布局:01 00 00 00 02 00 00 00...
读取的4字节:00 00 00 02
小端解析为:0x02000000(十进制33554432)

输出结果

printf("%x,%x", ptr1[-1], *ptr2);
  • ptr1[-1]输出4的十六进制形式:4
  • *ptr2输出0x02000000的十六进制形式:2000000(具体值依赖字节序)。

最终答案

在小端机器上,程序输出为:

4,2000000

注意:若平台禁止未对齐访问或使用大端序,结果可能不同。


3. 浮点数在内存中的存储

        常见的浮点数包括:3.14159、1E10等。
        浮点数家族的具体表示范围可在标准头文件 float.h 中查看定义。

3.1、练习1

#include <stdio.h>
int main()
{int n = 9;float* pFloat = (float*)&n;printf("n的值为:% d\n",n);printf("*pFloat的值为:% f\n",*pFloat);* pFloat = 9.0;printf("num的值为:% d\n",n);printf("*pFloat的值为:% f\n",*pFloat);
}

        代码功能分析

        该代码演示了C语言中指针类型转换对数据解释的影响,通过整型与浮点型的指针转换,展示同一内存空间在不同类型下的二进制解释差异。

关键代码解析

int n = 9;
float* pFloat = (float*)&n;

        将整型变量n的地址强制转换为浮点型指针,使同一内存空间可通过两种类型访问。由于整型和浮点型的二进制存储格式不同,会导致数据解释差异。

        第一次输出结果

printf("n的值为:%d\n",n);        // 输出整型值9
printf("*pFloat的值为:%f\n",*pFloat); // 输出将整型9的二进制解释为浮点数的结果

        整型9的二进制表示为00000000 00000000 00000000 00001001,按IEEE 754浮点数标准解释时,会得到一个极小的非规格化数(约1.4e-45)。

第二次输出结果

*pFloat = 9.0;
printf("num的值为:%d\n",n);      // 输出将浮点9.0的二进制解释为整数的结果
printf("*pFloat的值为:%f\n",*pFloat); // 正常输出浮点值9.0

        浮点数9.0的二进制表示为0 10000010 00100000 00000000 00000000(十六进制0x41100000),转为十进制整数值为1091567616

        核心原理

  • 类型双关(Type Punning):通过不同类型指针访问同一内存区域
  • IEEE 754标准:浮点数采用符号位+指数位+尾数位的存储格式
  • 整型存储:直接采用补码形式存储

典型运行结果:

n的值为:9
*pFloat的值为:0.000000
num的值为:1091567616
*pFloat的值为:9.000000

3.2 浮点数的存储

在上面的代码中,虽然num*pFloat指向的是内存中的同一个数,但为何浮点数与整数的解读结果会有如此大的差异?要理解这一现象,关键在于掌握浮点数在计算机内部的表示方法。

根据国际标准IEEE 754,任意一个二进制浮点数V可以表示为以下形式:

V = (-1)^S × M × 2^E

其中:

  • S表示符号位:当S=0时,V为正数;当S=1时,V为负数
  • M表示有效数字,其取值范围为1 ≤ M < 2
  • E表示指数位

举例说明:

  • 十进制数5.0的二进制表示为101.0,相当于1.01×2^2。根据上述公式,可得S=0,M=1.01,E=2。
  • 十进制数-5.0的二进制表示为-101.0,相当于-1.01×2^2。此时S=1,M=1.01,E=2。

IEEE 754标准规定:

  • 对于32位浮点数:
    • 最高1位存储符号位S
    • 接下来的8位存储指数E
    • 剩余的23位存储有效数字M
  • 对于64位浮点数:
    • 最高1位存储符号位S
    • 接下来的11位存储指数E
    • 剩余的52位存储有效数字M

3.2.1 浮点数存储过程

IEEE 754标准对有效数字M和指数E有特殊规定:

  1. 有效数字M的处理
    根据规定,1≤M<2,因此M可以表示为1.xxxxxx的形式(xxxxxx为小数部分)。存储时,默认第一位总是1,故只需保存后面的xxxxxx部分。例如存储1.01时,仅保存01,读取时再补回第一位。这种设计可节省一位存储空间:以32位浮点数为例,实际23位存储空间通过此法可保存24位有效数字。

  2. 指数E的处理
    E作为无符号整数,其存储需要特殊处理:

    • 8位E的取值范围:0~255
    • 11位E的取值范围:0~2047

    由于科学计数法允许负指数,IEEE 754规定存储时需将真实E值加上固定偏移量:

    • 8位E的偏移量:127
    • 11位E的偏移量:1023

    例如:2^10的E=10,在32位浮点数中存储为10+127=137(二进制10001001)。

3.2.2 浮点数的取过程

指数E的提取可分为三种情况:

  1. E不全为0且不全为1时: 浮点数采用以下规则表示:
    • 将指数E的计算值减去127(或1023)得到真实值
    • 在有效数字M前补上隐含的1

例如:0.5的二进制形式为0.1

  • 根据规范,正数部分必须为1,故将小数点右移1位得到1.0×2^(-1)
  • 阶码计算:-1 + 127 = 126,对应二进制01111110
  • 尾数处理:1.0去掉整数部分为0,补齐23个0得到00000000000000000000000 最终二进制表示为:0 01111110 00000000000000000000000
0 01111110 00000000000000000000000

        当E全为0时,浮点数的指数E实际值为1-127(或1-1023)。此时有效数字M不再需要补上首位的1,而是直接表示为0.xxxxxx的小数形式。这种设计是为了准确表示±0以及接近零的极小数值。

 0 11111111 00010000000000000000000

3.3 题目解析
        现在,让我们回到最初的练习题。

        首先看第一个问题:为什么将整数9转换为浮点数后,结果却显示为0.000000?

        这是由于整数9在内存中以整型形式存储时,对应的二进制序列为:

0000 0000 0000 0000 0000 0000 0000 1001

首先,将9的二进制序列按照浮点数格式进行拆分:

  • 符号位s=0(1位)
  • 指数E=00000000(8位)
  • 有效数字M=00000000000000000001001(23位)

由于指数E全为0,符合特殊情况。因此浮点数V的计算公式为: V = (-1)^0 × 0.00000000000000000001001 × 2^(-126) = 1.001 × 2^(-146) 显然,V是一个极小的正数,接近于0,故十进制表示为0.000000。

接下来分析浮点数9.0:

  • 二进制表示为1001.0
  • 科学计数法形式:1.001 × 2^3

因此: 9.0 = (-1)^0 × (1.001) × 2^3

各组成部分:

  • 符号位S=0
  • 有效数字M=001(补全至23位)
  • 指数E=3+127=130(二进制10000010)

最终二进制表示为S+E+M的组合。

 0 10000010 001 0000 0000 0000 0000 0000

这个32位二进制数作为整数解析时,以补码形式存储在内存中,其原码对应值为1091567616。

 

http://www.dtcms.com/a/403969.html

相关文章:

  • 石家庄网站制作哪家好wordpress 优化数据库
  • 《基于Qt的车载系统项目》
  • 有哪些免费推广软件网站seo推广排名
  • 41.传输层协议UDP
  • 优良的定制网站建设提供商c2c模式的网站
  • 记力扣2516.每种字符至少取k个 练习理解
  • 广州站电话科创纵横 网站建设
  • 进程与集群:提升性能
  • 北京建设信源官方网站如何让wordpress文本小工具支持php和简码?
  • NLP算法岗位面试题精讲:深入理解LoRA与QLoRA
  • 基于神经控制微分方程的采集无关深度学习用于定量MRI参数估计|文献速递-文献分享
  • 无锡嘉饰茂建设网站的公司天河区网站制作
  • 应用程序映像(Application Image)是什么?
  • 访问的网站显示建设中wordpress tag伪静态
  • 单调速率调度(RMS)算法
  • 百度智能云一念·智能创作平台
  • 做网站订阅号丰台建设企业网站
  • shell编程:sed - 流编辑器(2)
  • 在Grafana中配置MySQL数据源并创建查询面板
  • 做的比较好的二手交易网站有哪些小学学校网站建设计划书
  • OneSignal v2 PHP手搓请求消息推送-供参考
  • 中国建站公司wordpress主题 下单
  • Qt DPI相关逻辑
  • 约束优化问题的常用解决办法及优缺点、轨迹规划中应用
  • 电子元器件基础知识day1
  • 【C++游记】C++11特性
  • 光子、光量子、量子三者的关系
  • 网站更改目录做301承德信息网络有限公司
  • Pytorch中stack()方法的总结及理解
  • 网站建设需要那种技术开一个网站多少钱