您的位置:首页 > 房产 > 建筑 > 网页游戏排行榜13_爱站网工具包_南宁网站优化公司电话_今日头条重大消息

网页游戏排行榜13_爱站网工具包_南宁网站优化公司电话_今日头条重大消息

2025/2/27 3:53:35 来源:https://blog.csdn.net/weixin_44643253/article/details/145555337  浏览:    关键词:网页游戏排行榜13_爱站网工具包_南宁网站优化公司电话_今日头条重大消息
网页游戏排行榜13_爱站网工具包_南宁网站优化公司电话_今日头条重大消息

1. 整数在内存中的存储

在讲解操作符的时候,我们有两篇文章讲过了下面的内容:整数的2进制表示方法有三种,即 原码、反码和补码,分别是C语言第2节:数据类型和变量和C语言第10节:详解操作符,点击链接🔗即可查看之前的内容,这里我们再复习一下。

1.1 原码(Sign-Magnitude Representation)

  • 定义:最高位作为符号位,0 表示正数,1 表示负数,剩下的位表示数值的绝对值。
  • 特点:
    • 正数的原码和其二进制表示相同。
    • 负数的原码是符号位为 1,后面加上绝对值的二进制。

示例(8位)

  • +500000101
  • -510000101

问题:存在两个零+0-0),运算复杂。


1.2 反码(One’s Complement)

  • 定义:正数的反码与原码相同;负数的反码是对其原码除符号位外按位取反
  • 特点:
    • 用于解决两个零的问题,但仍需特殊处理。

示例(8位)

  • +500000101(同原码)
  • -5
    • 原码:10000101
    • 反码:11111010(符号位不变,其他位取反)

问题:仍需特殊规则处理零,运算复杂。


1.3 补码(Two’s Complement)

  • 定义:正数的补码与原码相同;负数的补码是对其绝对值的二进制表示按位取反后加1
  • 特点:
    • 简化了加减法运算。
    • 只有一个零(解决了两个零的问题)。
    • 符号位直接参与运算。

示例(8位)

  • +500000101(同原码)
  • -5
    • 原码:10000101
    • 反码:11111010
    • 补码:11111011(反码加1)

优势

  • 运算简单。
  • 广泛用于现代计算机系统。
  • 计算机存储的是补码

1.4 总结对比

表示方式最高位正数表示负数表示零的表示应用场景
原码符号位与二进制相同符号位+绝对值二进制+0 和 -0理论学习
反码符号位与二进制相同按位取反+0 和 -0早期计算机
补码符号位与二进制相同按位取反+1唯一的零现代计算机实际使用

对于整形来说:数据存放内存中其实存放的是二进制的补码。

为什么呢?

在计算机系统中,数值一律用补码来表示和存储。

原因在于,使用补码,可以将符号位和数值域统一处理;

同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

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

其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储,下面是内容讲解

2.1 什么是字节序?

字节序(Endianness)是多字节数据在内存中存储的顺序,即如何将一个多字节的数据(如 int, float)的每个字节存放在连续的内存单元中。

2.2 分类:大端和小端

  • 大端序(Big Endian)

    • 高位字节存储在低地址,低位字节存储在高地址。
    • 类似于我们平常书写数字的方式(从高位到低位)。

    例子0x12345678 (32位整数)

    • 内存存储顺序:12 34 56 78(地址从低到高)

      我这里没有对应的设备,无法实现,请见谅

  • 小端序(Little Endian)

    • 低位字节存储在低地址,高位字节存储在高地址。
    • 这是Intel x86架构等常见硬件的默认字节序。

    例子0x11223344 (32位整数)

    • 内存存储顺序:44 33 22 11(地址从低到高)

      在这里插入图片描述

2.3 字节序的影响与大小端的成因

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit 位,但是在C语言中除了 8bitchar 之外,还有16 bitshort 型,32 bitlong 型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

  • 在大端系统中
    • 数据按照从高位到低位的顺序存储。
    • 网络字节序(Network Byte Order)通常采用大端。
  • 在小端系统中
    • 数据按照从低位到高位的顺序存储。
    • 小端序是现代PC和嵌入式系统的常见存储方式。

2.4 字节序的具体存储示例

示例:存储 int x = 0x12345678;

假设变量 x 的地址是 0x1000int 类型为 4 字节:

地址0x10000x10010x10020x1003
大端序0x120x340x560x78
小端序0x780x560x340x12

2.5 总结

特点大端序(Big Endian)小端序(Little Endian)
存储顺序高位字节存储在低地址,高地址存储低位字节低位字节存储在低地址,高地址存储高位字节
常见应用网络协议、部分嵌入式系统x86、ARM(默认小端)、现代PC架构
优点人类易读(类似自然书写方式)存取效率高(低位先存取)

2.6 练习

2.6.1 练习1

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

答:大端字节序存储:把一个数据的低位字节的数据存放在高地址处,高位字节的数据存放在低地址处;小端序节序存储:把一个数据的低位字节的数据存放在低地址处,高位字节的数据存放在高地址处。

//代码1
#include <stdio.h>
int check_sys()
{int i = 1;return (*(char *)&i);
}
int main(){int ret = check_sys();if(ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}

解释:

  • 在函数check_sys中,int型变量i的值为1,那么它在内存中的表示为0x00000001
  • 如果是大端,那么最低位置存储的值是 0
  • 如果是小端,那么最低位置存储的值是 1
//代码2
int check_sys()
{union{int i;char c;}un;un.i = 1;return un.c;
}

解释:

  • 联合体的所有成员共享同一块内存区域。(以后会讲解联合体)

  • i = 1 时,其二进制表示为:0x00000001(在 32 位或 16 位系统中)。

  • 系统的字节序决定了最低字节的位置:

    • 小端序:最低有效字节 0x01 存储在低地址。
    • 大端序:最高有效字节存储在低地址,0x01 存储在高地址。
  • 如果 un.c 的值为 1,说明低地址存储低字节,即小端序

    如果 un.c 的值为 0,说明低地址存储高字节,即大端序

2.6.2 练习2

//VS2022
#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.6.2.1 ab

在带符号类型中,计算机使用 补码 来表示负数。我们通过以下步骤理解:

  1. -1 的补码表示:
    • 原码:对于 -1,其原码是 10000001。第一个 1 是符号位,表示负数,后面 7 个 0 表示数值 1。
    • 反码:反码是对原码中除符号位外的每一位取反,-1 的反码是 11111110
    • 补码:补码是反码加 1,-1 的补码是 11111111
  2. 如何存储 -1
    • ab 都是带符号类型(8 位),所以它们的值 -1 会被存储为 11111111(补码表示)。
    • 在 8 位补码表示下,11111111-1 的补码表示。无论是 char 还是 signed char,它们会存储为 11111111,并且打印出来时将被解释为 -1
2.6.2.2 c

在无符号类型中,补码 不用于表示负数。无符号类型直接按二进制表示数值,不允许表示负数。因此,对于 unsigned char,负数会转换为它对应的无符号值。

  1. -1 的处理:
    • -1 被赋给 unsigned char 时,-1 会被转化为 8 位的补码表示(即 11111111)。
    • 然而,由于 unsigned char 类型不能表示负数,它会把这个补码当作无符号数值 11111111 来处理,即 255(这是 11111111 的无符号表示)。
  2. 如何存储 -1
    • 对于 unsigned char c-1 经过转换后会存储为 11111111,但它表示的值是 255,这是因为 unsigned char 类型解释的是 8 位二进制数的无符号值。

tips : 为什么a、b、c二进制存储一致?原因在于计算机底层只存储二进制数据,而数据类型只是用来告诉编译器如何解释这些二进制数据

2.6.2.3 整形提升与输出

printf函数中,给它传递参数的时候,会将abc进行整形提升。由于整形提升的规则,尽管我们传递的是 charunsigned char 类型变量,但它们在传递给 printf 时会被提升为 intunsigned int

ab 的输出

  • achar 类型printf 期望一个带符号 int,因此 a 在传递给 printf 时会被提升为 int。由于 a 存储的是 -1(在补码中表示为 11111111),它在整形提升后仍然是 -1(补码为11111111 11111111 11111111 11111111)。因此,a 打印出来时是 -1
  • bsigned char 类型,同样会被提升为 int 类型,在传递给 printf 时也会是 -1。因此,b 也打印出 -1

c 的输出(无符号整数 %d

  • cunsigned char 类型,根据整形提升规则,c 会被提升为 unsigned int 类型。尽管 c 的存储值是 11111111(即 255),它会被提升为 unsigned int,因此 c 被提升后作为无符号整数 255 传递给 printf,最终输出 255

最终输出结果为:

a=-1,b=-1,c=255

2.6.3 练习3

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

解释:

根据上一个题的方法,我们可以轻松的得到

  • a的原码为10000000
  • a的反码为11111111
  • a的补码为10000000

(如果不知道怎么计算,可以先计算4字节存储的原码反码补码,再进行截断来得到1字节的原码反码补码)

整形提升(Integer Promotion)

  • 在调用 printf("%u\n", a) 时,a 作为参数传递,会发生整形提升

  • 而MSVC中char 是有符号类型(signed char),并且补码最高位为1(即负数),整形提升后会用符号扩展填充高位。

  • 提升后的补码为11111111 11111111 11111111 10000000(即int类型的-128),当这个 int 类型的 -128 被打印为无符号整数时,它会被解释为一个 无符号整数,即 4294967168

输出结果:

4294967168

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

解释:

  • 有符号char范围是-128 ~ 127。赋值 128 超出了 signed char 的范围,因此发生溢出。由于 char 是 1 字节(8 位),只保留 低 8 位有效值,多余的高位会被丢弃,因此二进制存储为 10000000。而二进制 10000000 对应的补码解释为 -128。因此,a 实际存储的值是 -128
  • a 的原码、反码、补码
    • a的原码为10000000

    • a的反码为11111111

    • a的补码为10000000

整形提升(Integer Promotion)

  • 在调用 printf("%u\n", a) 时,a 作为参数传递,会发生整形提升

  • 而MSVC中char 是有符号类型(signed char),并且补码最高位为1(即负数),整形提升后会用符号扩展填充高位。

  • 提升后的补码为11111111 11111111 11111111 10000000(即int类型的-128),当这个 int 类型的 -128 被打印为无符号整数时,它会被解释为一个 无符号整数,即 4294967168

输出结果:

4294967168

2.6.4 练习4

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

解释:

  • 很明显,for循环尝试把-1~-1000的值存储在a这个字符数组里面

在这里插入图片描述

  • 但是有符号char范围是-128 ~ 127。赋值小于-128的数字超出了 signed char 的范围,因此发生溢出,发生溢出时,会按照补码规则截断。我们同样可以按照原码反码补码的方式来分析到底存储的值是什么。

  • 这里我们以-129为例,-129的原码是 10000000 00000000 00000000 10000001 反码是 11111111 11111111 11111111 01111110 补码是11111111 11111111 11111111 01111111,存储在1字节的char类型变量里的补码则是01111111,对应存储的数据实际上是127。同理可得-130为126······

    右图引用《C++ Primer Plus》中的插图,以短整型为例直观感受溢出时实际的值
  • 所以溢出的具体表现为超过 -128 的值会从 127 开始循环。a[127] = -128a[128] = 127a[129] = 126,依此类推。

    在这里插入图片描述

  • strlen 计算字符串长度的方式是:从数组的第一个字符开始,逐个检查字符是否是 \0(ASCII 值为 0)。一旦遇到 \0,停止计数,返回当前的字符数。根据赋值 -1 - i 的计算:当 i = 255时:-1 - 255 = -256按char 类型截断后,值为 0(补码为 00000000)。因此,a[255] 是第一个 \0。从a[0]~a[255],一共有256个字符,除去a[255] 的第一个 \0,一个255个

程序最终输出:

255

2.6.5 练习5

#include <stdio.h>unsigned char i = 0;
int main()
{for(i = 0;i<=255;i++){printf("hello world\n");}return 0;
}

解释:

(1) 循环条件和增量

  • 循环从 i = 0 开始,每次执行 i++,直到条件 i <= 255 不再成立。
  • 循环条件 i <= 255 看起来是希望 i 的值从 0255,总共执行 256 次。

(2) 循环的陷阱:i 的类型是 unsigned char

  • unsigned char 的最大取值为 255
  • i增加到 255 时,再执行i++时会导致溢出。
    • 因为 unsigned char 只有 8 位,当 i = 255 时再加 1,溢出后会变为 0,而不是 256。(可以自己用原码反码补码进行分析)
  • 这意味着变量 i 的值会从 255 回到 0,并进入下一次循环。

(3) 无限循环的原因

  • 由于 iunsigned char,它的取值范围是 0 ~ 255
  • i = 255 时,再加 1 会导致溢出并重新回到 0
  • 因为 for 循环的条件是 i <= 255,而 i 的值一直在 0 ~ 255 之间变化,因此循环永远不会终止,会陷入 无限循环

(4) 程序的输出

  • 在每次循环中,都会执行 printf("hello world\n");
  • 因为变量 i 永远不会超出 0 ~ 255 的范围,并且 i <= 255 一直成立,所以程序会无限输出 "hello world"

#include <stdio.h>
int main()
{unsigned int i;for(i = 9; i >= 0; i--){printf("%u\n",i);}return 0;
}

解释:

1. 变量 i 的类型

  • 这里 i 被定义为 unsigned int,即 无符号整数,它的取值范围是从 02^32 - 1(通常是 0 ~ 4294967295)。
  • 因为它是无符号的,所以 i 无法表示负值

2. 循环行为分析

  • 初始化i9 开始。
  • 循环条件i >= 0,因为 i 是无符号整数,这个条件实际上永远为真,因为无符号整数的最小值是 0
  • 步进:每次循环后 i--,意味着 i 的值会递减。
  1. i9 递减到 0,打印 0 后,接下来执行 i--
  2. 溢出行为i 是无符号类型,所以当 i0 递减时,会发生 溢出,使得 i 变为其最大值,即 4294967295。(可以自己用原码反码补码进行分析)
  3. 因此,i4294967295 继续递减,每次仍满足 i >= 0 的条件,导致无限循环

3. 输出结果

  • 循环开始时,程序依次打印 90
  • 然后,由于 i 溢出变为 4294967295,程序会继续无限递减并打印这些值。

2.6.6 练习6

#include <stdio.h>
//X86环境 小端字节序
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;
}

代码输出的结果是啥?

1. ptr1[-1]

  • &a + 1
    • &a 是指向整个数组的指针,其类型是 int (*)[4],即指向 4 个整数的数组。
    • &a + 1 指向的是数组 a 之后的内存位置,相当于原来地址加上 16 字节(4 个 int,每个 4 字节)。
  • ptr1 = (int *)(&a + 1)
    • &a + 1 强制转换为 int *,所以 ptr1 指向数组 a 之后的位置,即 a[4] 的地址。
  • ptr1[-1]
    • ptr1[-1] 等价于 *(ptr1 - 1),即指向 a[3],即数组 a 的最后一个元素。
    • 因此,ptr1[-1] 的值是 4

2. *ptr2

  • (int)a

    • a 是数组名,指向数组的首地址,其类型是 int *
    • (int)a 是将 a 的地址强制转换为 int 类型。需要注意,这里直接将一个指针转换为整数,这种操作在不同的编译器和系统环境中可能会有不同的行为。
  • (int)a + 1

    • 将地址值加 1,相当于将原来的首地址偏移了 1 个字节
  • (int *)((int)a + 1)

    • 现在 (int)a + 1 是一个偏移后的整数值,将它转换回 int *
    • 这样 ptr2 指向的实际上是一个不对齐的位置,因为原始地址加 1 字节后再去读取 int 类型,可能会导致未对齐访问
  • 数组 a 的第一个元素是 1,在内存中按小端存储,即内存中的布局是从低字节到高字节的存储,其十六进制表示为:01 00 00 00

  • 偏移后的地址访问:

    • (int)a + 1 后的地址指向第二个字节(即 00)。
    • 因为 ptr2 被解释为 int * 类型,所以它试图从这个地址读取 4 个字节
    • 由于这个地址是字节级的偏移,读取到的值为 2000000

在这里插入图片描述

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

浮点数在 C 语言中的存储遵循 IEEE(电气和电子工程协会) 754 标准,主要包括 单精度浮点数(32 位)双精度浮点数(64 位) 两种格式。这里将详细介绍浮点数的存储方式、组成部分以及如何存储和表示浮点数。

3.1 IEEE 754 标准

IEEE 754 标准是浮点数在计算机中表示的标准。它定义了浮点数的表示格式,分为两种主要格式:

  • 单精度(32 位):用于存储较低精度的浮点数,使用 32 位来存储。
  • 双精度(64 位):用于存储更高精度的浮点数,使用 64 位来存储。

3.2 浮点数的组成部分

浮点数的存储由三部分组成:符号位指数部分尾数部分(也称为有效数字)
V = ( − 1 ) S ∗ M ∗ 2 E V = (-1) ^ S * M * 2 ^ E V=(1)SM2E
(1) 符号位(S)

  • 用于表示浮点数的正负。
    • 如果符号位是 0,表示正数。
    • 如果符号位是 1,表示负数。

(2) 指数部分(E)

  • 指数部分表示浮点数的 范围,它采用 偏移量表示法

    偏移量的作用是将指数部分的值映射到一个正数范围(无符号整数),这样就不需要直接存储负数的指数值。

    偏移量(bias)对于不同的浮点数格式是不同的:

    • 单精度浮点数:偏移量为 127(即 2^7 - 1)。
    • 双精度浮点数:偏移量为 1023(即 2^10 - 1)。

(3) 尾数部分(M)

  • 也叫有效数字,表示浮点数的精确值。尾数通常是一个二进制的小数。

    • 在单精度浮点数中,尾数使用 23 位。
    • 在双精度浮点数中,尾数使用 52 位。
  • M是大于等于1,小于2的。

3.3 浮点数存储方式

3.3.1 单精度浮点数(32 位)的存储结构

单精度浮点数占 32 位,其中:

  • 1 位用于符号位(S)
  • 8 位用于指数部分(E)
  • 23 位用于尾数部分(M)

其存储结构如下:

符号位 (S)指数部分 (E)尾数部分 (M)
1 位8 位23 位

3.3.2 双精度浮点数(64 位)的存储结构

双精度浮点数占 64 位,其中:

  • 1 位用于符号位(S)
  • 11 位用于指数部分(E)
  • 52 位用于尾数部分(M)

其存储结构如下:

符号位 (S)指数部分 (E)尾数部分 (M)
1 位11 位52 位

3.4 浮点数表示方法(规格化与非规格化)

在 IEEE 754 标准中,浮点数分为 规格化数(Normalized)和 非规格化数(Denormalized)。

3.4.1 规格化数(Normalized)

  • 在规格化数中,尾数部分(M)始终以 1 开头。即,尾数部分通常表示为 1.xxxxxx(二进制)。这个 1隐含的,在存储时不会显式表示。只保存后面的xxxxxx部分。(比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。)
  • 指数部分是存储一个偏移量,通常加上一个常数(例如:对于 32 位的浮点数,偏移量为 127;对于 64 位的浮点数,偏移量为 1023)。

例如,浮点数 1.25 可以表示为:

  • 1.25 = 1.01 × 2^0

该值在内存中的表示为:

  • 符号位 S = 0(表示正数)
  • 指数部分 E = 127(偏移量表示法)
  • 尾数部分 M = 01000000000000000000000(隐含 1)

3.4.2 非规格化数(Denormalized)

  • 当指数部分为全 0 时,表示非规格化数。在这种情况下,尾数部分的前导 1 不再隐含,实际存储的尾数以 0 开头。这样做是为了表示±0,以及接近于0的很小的数字。
  • 非规格化数可以表示非常接近零的小数值,但精度较低。

3.4.3 特殊数

在 IEEE 754 标准中,当 指数部分全为 1 时,表示两种特殊的浮点数:无穷大(Infinity)和 NaN(Not a Number,非数)。

这两种特殊值通常用于表示计算中出现的错误或溢出的结果。

3.4.3.1 指数部分全为 1 的含义

(1) 单精度浮点数(32 位)

单精度浮点数的存储格式如下:

  • 1 位符号位
  • 8 位指数部分
  • 23 位尾数部分(有效数字)

如果指数部分的 8 位全为 1,即 11111111,我们需要根据符号位和尾数部分的值来区分具体情况:

  • 符号位 = 0,尾数部分全为 0:表示正无穷大(+∞)。
  • 符号位 = 1,尾数部分全为 0:表示负无穷大(-∞)。
  • 尾数部分不全为 0:表示 NaN(非数),这是一个特殊的数值,表示不合法或未定义的数学操作(例如 0 除以 0)。

(2) 双精度浮点数(64 位)

双精度浮点数的存储格式如下:

  • 1 位符号位
  • 11 位指数部分
  • 52 位尾数部分

如果指数部分的 11 位全为 1,即 11111111111,同样地:

  • 符号位 = 0,尾数部分全为 0:表示正无穷大(+∞)。
  • 符号位 = 1,尾数部分全为 0:表示负无穷大(-∞)。
  • 尾数部分不全为 0:表示 NaN(非数)。
3.4.3.2 解释:无穷大(Infinity)和 NaN

(1) 无穷大(Infinity)

  • 无穷大用于表示超出浮点数表示范围的值,例如在除法中出现溢出或者数字超出了可以表示的范围。
    • +∞:符号位为 0,指数部分为 11111111,尾数部分为00000000000000000000000
    • -∞:符号位为 1,指数部分为 11111111,尾数部分为 00000000000000000000000

(2) NaN(Not a Number)

  • NaN 表示无效或未定义的数值,通常出现在无法计算或出现数学错误的情况下。例如,尝试计算 0 / 0∞ - ∞sqrt(-1) 等。
  • NaN 的符号位可以是 01,但尾数部分不能全为 0。
    • 常见的 NaN 格式为:指数部分为全 1,尾数部分非全零。
3.4.3.3 举个例子

假设我们使用单精度浮点数(32 位)表示:

(1) 正无穷大(+∞)

  • 符号位:0(表示正数)
  • 指数部分:11111111(表示
  • 尾数部分:00000000000000000000000(尾数全为 0)

二进制表示:0 11111111 00000000000000000000000

(2) 负无穷大(-∞)

  • 符号位:1(表示负数)
  • 指数部分:11111111(表示
  • 尾数部分:00000000000000000000000(尾数全为 0)

二进制表示:1 11111111 00000000000000000000000

(3) NaN(非数)

  • 符号位:0(可以是 01
  • 指数部分:11111111(表示特殊情况)
  • 尾数部分:非全 0(尾数部分有有效数字)

二进制表示(一个示例):0 11111111 1000000000000000000000010000000000000000000000 作为 NaN 的一部分)

3.5 浮点数表示的例子

3.5.1单精度浮点数:1.25

假设我们有浮点数 1.25,我们想将它表示为一个单精度浮点数。

  1. 浮点数转换为二进制
    1.25 转换为二进制是 1.01,即 1.01 × 2^0
  2. 规格化表示
    这是一个规格化浮点数,因为尾数是 1.01。根据 IEEE 754 标准,尾数是 1.xxxx 格式。
  3. 指数部分
    指数为 0,但需要加上偏移量 127(单精度浮点数的偏移量),所以存储的指数部分是 127,即二进制 01111111
  4. 尾数部分
    尾数部分是 1.01,由于隐含的 1,存储的实际尾数部分是 01000000000000000000000(23 位)。
  5. 最终表示
    • 符号位:0(正数)
    • 指数部分:01111111
    • 尾数部分:01000000000000000000000

在内存中的存储方式是:

符号位 (S)指数部分 (E)尾数部分 (M)
00111111101000000000000000000000

32 位二进制表示0 01111111 01000000000000000000000

3.5.2 双精度浮点数:1.25

对于双精度浮点数,存储格式是相同的,只是占用 64 位空间。

  1. 浮点数转换为二进制
    1.25 转换为二进制是 1.01 × 2^0
  2. 规格化表示
    规格化浮点数,尾数是 1.01,隐含的 1
  3. 指数部分
    偏移量为 1023(双精度的偏移量),因此存储的指数部分是 1023 + 0 = 1023,即二进制 01111111111
  4. 尾数部分
    尾数部分为 1.01,实际存储尾数部分为 0100000000000000000000000000000000000000000000000000(52 位)。
  5. 最终表示
    • 符号位:0(正数)
    • 指数部分:01111111111
    • 尾数部分:0100000000000000000000000000000000000000000000000000

在内存中的存储方式是:

符号位 (S)指数部分 (E)尾数部分 (M)
0011111111110100000000000000000000000000000000000000000000000000

64 位二进制表示0 01111111111 0100000000000000000000000000000000000000000000000000

3.6 小练习

#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("n的值为:%d\n",n);printf("*pFloat的值为:%f\n",*pFloat);return 0;
}

这段代码输出什么?

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

3.6.1 题目解析

9以整型的形式存储在内存中,得到如下二进制序列:

0000 0000 0000 0000 0000 0000 0000 1001
3.6.1.1 int n = 9;

这一行代码定义了一个整数变量 n 并初始化为 9

  • n 的内存表示是 00000000 00000000 00000000 00001001(在 32 位机器上,9 的二进制表示为 00000000 00000000 00000000 00001001,具体根据平台的大小端可能会有所不同)。
  • 也就是说,n 在内存中的字节顺序是:0x09 0x00 0x00 0x00
3.6.1.2 float *pFloat = (float *)&n;

这行代码做了一个 强制类型转换,将 n 的地址(&n)强制转换为 float * 类型。

  • &n 是变量 n 的地址,它是 int * 类型,但我们将其转换成了 float * 类型。
  • 类型转换:虽然 n 是一个 int 类型(4 字节),但我们将它的地址当作 float 类型来访问。注意:float 类型占用 4 字节,但它的存储方式与 int 类型可能不同,float 类型的存储采用 IEEE 754 标准,而 int 类型是按照补码表示的。因此,这两种类型在内存中的解释方式会不同。
3.6.1.3 printf("n的值为:%d\n", n);

这行代码输出变量 n 的值。在此时,n 的值是 9,输出的结果为:

n的值为:9
3.6.1.4 printf("*pFloat的值为:%f\n", *pFloat);
  • *pFloat 解引用指针 pFloat,并将其作为 float 类型的值来打印。
  • 由于 pFloat 是指向 nfloat * 指针,程序会把 n 的内存内容按照 float 类型进行解释。n 的内存内容是 0000 0000 0000 0000 1 0000 0000 0000 1001,当以 float 类型读取时,程序会将这 4 个字节解析为一个 float 数值,而不是 int 数值。
  • 首先,将9 的二进制序列按照浮点数的形式拆分,得到第一位符号位s =0,后面8位的指数E=00000000,最后23位的有效数字M=000 0000 0000 0000 0000 1001
  • 由于 0000 0000 0000 0000 1 0000 0000 0000 1001 的二进制位在 IEEE 754 标准下对应的浮点数值非常接近 1.401298e-45(这是一个非常小的数,接近于零),输出的值是非常小的浮点数,而不是预期的 9.0

因此,输出为:

*pFloat的值为:0.000000
3.6.1.5 *pFloat = 9.0;

这行代码通过 pFloat 指针将 9.0 赋值给 n

  • 由于 pFloat 指向 n,这实际上是修改了 n 的值,赋值为 9.0

  • 这里,9.0 被赋给了 n,但是由于 pFloatfloat * 类型的指针,赋值时会将 9.0 转换为其 IEEE 754 浮点数的二进制表示,并写回到 n 的内存位置。

  • 首先,浮点数9.0 等于二进制的1001.0,即换算成科学计数法是:
    9.0 = ( − 1 ) 0 ∗ 1.001 ∗ 2 3 9.0 = (-1)^0 * 1.001 * 2^3 9.0=(1)01.00123

  • 9.0 在 IEEE 754 单精度浮点数下的表示是:0 10000010 001 0000 0000 0000 0000 0000。程序会将这四个字节写入 n 的内存。

3.6.1.6 printf("n的值为:%d\n", n);

这行代码再次打印 n 的值。此时,n 被视作一个 int 类型,但它的内存内容被修改为 9.0 的浮点数二进制表示。因此,输出的 n 的值实际上是浮点数的二进制表示作为整数解释的结果。

  • 浮点数 9.0 的 IEEE 754 单精度浮点数表示是0 10000010 001 0000 0000 0000 0000 0000,它的字节顺序会被解释为一个 int 类型。
  • 0 10000010 001 0000 0000 0000 0000 0000 在内存中按 int 类型解释时,实际上是 1091567616

因此,输出为:

n的值为:1091567616
3.6.1.7 printf("*pFloat的值为:%f\n", *pFloat);
  • 现在,*pFloat 是将 n 的内存内容作为 float 类型读取。
  • 由于 n 的内存内容现在已经是浮点数 9.0 的二进制表示,*pFloat 会正确地输出 9.0

因此,输出为:

*pFloat的值为:9.000000

—完—

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com