【C语言进阶】整数与浮点数在内存中的存储
本章目标:
学习并且掌握数据在内存中的存储(整型与浮点型),以及学会判断机器大小端字节序。
本章重点:
1.数据类型的介绍
2.整型数据在内存中的存储:原码、反码、补码
3.大小端字节序的介绍以及如何判断
4.浮点型在内存中的存储解析
正文:
一、数据类型介绍
1 基本内置类型
类型明显或隐含地规定了数据的取值范围、存储方式以及允许进行的运算。
示例:
char //字符型
short //短整型
int //整型
long //长整型
long long //长长整型
float //单精度浮点型
double //双精度浮点型
说明:
1 char
的取值范围:-128 ~ 127
2 short
的取值范围:-32768 ~ 32767
3 int
的取值范围:-2147483648 ~ 2147483647
4 C语言标准规定:sizeof(long) >= sizeof(int)
类型的意义:
1.使用这个类型能开辟多大的内存空间(大小决定了使用范围)。
2.看待内存空间的视角。
2 包含整型或浮点型信息的头文件
C头文件 <limits.h>
和 <float.h>
分别提供了整数类型和浮点类型大小限制相关的详细信息。
每个头文件都定义了一系列供实现使用的符号常量。
2.1 <limits.h>
中的一些符号常量
符号常量 | 含义 |
---|---|
CHAR_BIT | char 类型的位数 |
CHAR_MAX | char 类型的最大值 |
CHAR_MIN | char 类型的最小值 |
SCHAR_MAX | signed char 类型的最大值 |
SCHAR_MIN | signed char 类型的最小值 |
UCHAR_MAX | unsigned char 类型的最大值 |
SHRT_MAX | short 类型的最大值 |
SHRT_MIN | short 类型的最小值 |
USHRT_MAX | unsigned short 类型的最大值 |
INT_MAX | int 类型的最大值 |
INT_MIN | int 类型的最小值 |
UINT_MAX | unsigned int 类型的最大值 |
LONG_MAX | long 类型的最大值 |
LONG_MIN | long 类型的最小值 |
ULONG_MAX | unsigned long 类型的最大值 |
LLONG_MAX | long long 类型的最大值 |
LLONG_MIN | long long 类型的最小值 |
ULLONG_MAX | unsigned long long 类型的最大值 |
2.2 <float.h>
中的一些符号常量
符号常量 | 含义 |
---|---|
FLT_MANT_DIG | float 类型的尾数位数 |
FLT_DIG | float 类型的最少有效数字位数(十进制) |
FLT_MIN_10_EXP | 带全部有效数字的 float 类型的最小负指数(以 10 为底) |
FLT_MAX_10_EXP | float 类型的最大正指数(以 10 为底) |
FLT_MIN | 保留全部精度的 float 类型最小正数 |
FLT_MAX | float 类型的最大正数 |
FLT_EPSILON | 1.00 和比 1.00 大的最小 float 类型值之间的差值 |
说明:
1 表中所列都与 float
类型相关。
2 把符号常量中的 FLT
分别替换成 DBL
和 LDBL
,即可分别表示 double
和 long double
类型对应的符号常量。
3 类型的基本归类
3.1 整型家族
示例:
char
unsigned char
signed char
short
unsigned short [int] <==> unsigned short
signed short [int] <==> short
int
unsigned int <==> unsigned
signed int <==> int
long
unsigned long [int] <==> unsigned long
signed long [int] <==> long
//unsigned - 无符号
//signed - 有符号
说明:
1 一般来说,char
到底是 unsigned char
还是 signed char
是不确定的,这取决于编译器的实现,C语言标准并未给出规定。
2 在VS编译器上,char
相当于是 signed char
。
3 对于整型家族,才有 unsigned
有符号的、signed
无符号的类型修饰符。
3.2 浮点数家族
示例:
float
double
long double
3.3 构造类型(自定义类型)
示例:
//1.数组类型
//2.结构体类型 struct
//3.枚举类型 enum
//4.联合类型 union (也称为共用体)
说明:
1 构造类型是用户自定义的类型。
2 对于数组类型:
int arr[10]; //数组类型是:int [10]
char ch[5]; //数组类型是:char [5]
//元素类型不同、元素个数不同,那么元素组成的数组的类型就不同。
3.4 指针类型
示例:
int* pi;
char* pc;
float* pf;
void* pv;
//结构体指针变量
3.5 空类型
void
表示空类型(无类型)
通常用于表示函数的返回值类型、函数的参数、指针类型。
void func(void)//函数的返回类型、参数类型
{
void* p = NULL; //指针类型
}
二、整型在内存中的存储
一个变量的创建会在内存中开辟内存空间。空间的大小是根据不同的类型所确定的。
1 原码、反码、补码
计算机中的整数有三种二进制表示形式:原码、反码、补码。三种表示方法均有符号位和数值位。
-
有符号数的最高位为符号位,其余位为数值位;无符号数所有位都为数值位。
-
符号位为0表示正数,符号位为1表示负数。
1.1 正数
正整数的原码、反码、补码相同。
1.2 负数
负整数的三种表示方法各不相同,需要进行计算。
原码:
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码:
原码的符号位不变,其他位按位取反就可以得到反码。
补码:
反码+1就可以得到补码。
2 整型数据在内存中的存储
对于整型数据来说,数据在内存中存放的是其二进制补码。
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于使用补码可以将符号位和数值位统一就进行处理;
同时,加法和减法运算也可以统一处理,因为CPU只有加法器;此外,补码与原码相互转换,其运算过程是相同的( 符号位不变,数值位取反后+1 ),不需要额外的硬件电路。
示例:
//计算1-1 --> 1+(-1)
//
//原码计算
// 00000000 00000000 00000000 00000001 -- 1的原码
// 10000000 00000000 00000000 00000001 -- -1的原码
//计算结果是:-2
// 10000000 00000000 00000000 00000010 -- -2的原码
//
//补码计算
// 00000000 00000000 00000000 00000001 -- 1的补码
// 11111111 11111111 11111111 11111111 -- -1的补码
//计算结果是:0
//1 00000000 00000000 00000000 00000000 -- 0的补码
//
说明:
以原码的形式进行数值运算的结果是错误的,而以补码的形式进行数值运算的结果是正确的。
3 大小端存储
3.1 大小端介绍
3.1.1 什么是大小端
大端(存储)模式:是指数据的低位保存在内存中的高地址处,数据的高位保存在内存中的低地址处。
小端(存储)模式:是指数据的低位保存在内存中的低地址处,数据的高位保存在内存中的高地址处。
示例:
//十进制 --> 二进制
10 --> 1010
//对于整数(包括十进制和二进制等)来说,其数位从最右侧起为低位,向左依次增加。
图示:
3.1.2 为什么会有大小端(了解一下)
为什么有大小端之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着1个字节,1个字节为8bit。但是在C语言中,除了 8bit 的 char 之外,还有 16bit 的 short,32bit 的 long(具体看编译器);
另外,对于位数大于8位的处理器,例如16位和32位的处理器,由于寄存器宽度大于1个字节,那么必然存在着一个如何将多个字节进行安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16bit 的 short 型变量 x ,在内存中的地址为 0x0010,而 x 的值为 0x1122,那么 0x11 为高字节,0x22 为低字节。
对于大端模式,就将 0x11 存放在低地址处,即 0x0010 中;将 0x22 存放在高地址处,即 0x0011 中。
对于小端模式,刚好相反。
我们常用的 X86 结构就是小端模式,而 KEIL C51 则为大端模式。很多的 ARM,DSP 都为小端模式。有些 ARM 处理器还可以由硬件来选择是大端模式还是小端模式。
3.2 示例
代码示例:
#include<stdio.h>
int main()
{
int a = 20;
//00000000 00000000 00000000 00010100 -- 20的补码(原码)
// //0 0 0 0 0 0 1 4 -- 十六进制表示
//0X 00 00 00 14
//
int b = -10;
//10000000 00000000 00000000 00001010 -- -10的原码
//11111111 11111111 11111111 11110101 -- -10的反码
//11111111 11111111 11111111 11110110 -- -10的补码
//F F F F F F F 6 -- 十六进制表示
//0X FF FF FF F6
return 0;
}
解释说明:
1 我们发现,20和-10的十六进制表示形式分别为 0X 00 00 00 14
和 0X FF FF FF F6
但是在内存中存储的位置好像有些不同。
3.3 笔试题
百度系统工程师笔试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)
3.3.1 方法一
代码示例:
#include<stdio.h>
int check_sys()
{
int i = 1;
//00000000 00000000 00000000 00000001 - 1的补码
//00 00 00 01
//
return *(char*)&i;
//返回1表示小端存储
//返回0表示大端存储
//对低位地址进行解引用,得到的是1就说明是小端存储,得到的是0就说明是大端存储
}
int main()
{
//判断机器大小端示例1
int ret = check_sys();
if (ret == 1)
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
解释说明:
1 本机器( VS2019环境 )是小端存储。
2 &i
为 int*
型数据,(char*)
强制类型转换为 char*
类型,再进行解引用访问一个 char
也就是1个字节的大小。
3 整数1在内存中的存储:
//00000000 00000000 00000000 00000001 -- 1的补码(原码)
//0 0 0 0 0 0 0 1 -- 转换为十六进制
//0x 00 00 00 01
我们发现整数1在内存中存储的值的十六进制形式是:00 00 00 01,当我们用一个 char
类型访问存放1的内存空间时,只能取出1个字节,也就是2个十六进制数,要么取低位的01,要么取高位的00。这样就能通过返回的值获得高位或者低位的2个十六进制数( 01或00 ),从而判断出是大端还是小端存储。
图示说明:
1 大端存储
2 小端存储
3.3.2 方法二
代码示例:
#include<stdio.h>
int check_sys()
{
//声明一个联合体
union
{
int i;
char c;
}un = { .i = 1 };//创建一个联合体变量,给成员i赋值为1
return un.c;//返回联合体变量成员c的值
}
int main()
{
//判断机器大小端示例2
int ret = check_sys();
if (ret == 1)
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
解释说明:
1 使用联合体(共用体),联合体成员 i
与 成员 c
共用同一块地址空间。
当用 char
型的 c
进行访问访问值为1的空间时,要么取低位的01,要么取高位的00。这样就能通过返回的值获得高位或者低位的2个十六进制数( 01或00 ),从而判断出是大端还是小端存储。
3.4 练习题
3.4.1 练习一
代码示例:
#include<stdio.h>
int main()
{
//练习一
char a = -1;
//10000000 00000000 00000000 00000001 - -1的原码
//11111111 11111111 11111111 11111110 - -1的反码
//11111111 11111111 11111111 11111111 - -1的补码
//存储时发生截断,截取整数-1补码的低八位:
//11111111
//
signed char b = -1;
//11111111 11111111 11111111 11111111 - -1的补码
//存储时发生截断,截取整数-1补码的低八位:
//11111111
//
unsigned char c = -1;
//11111111 11111111 11111111 11111111 - -1的补码
//存储时发生截断,截取整数-1补码的低八位:
//11111111
//
printf("a = %d, b = %d, c = %d\n", a, b, c);//-1 -1 255
//%d 以十进制形式打印有符号的整数
//对于非整型的变量a、b、c来说,此时发生了整型提升
//
//a为有符号的char,在整型提升的时候,高位补充符号位,此处符号位为1
//11111111 11111111 11111111 11111111 - 补码
//%d将该补码看作是有符号的整数:
//10000000 00000000 00000000 00000001 - -1的原码
//
//b为有符号的char,在整型提升的时候,高位补充符号位,此处符号位为1
//11111111 11111111 11111111 11111111 - 补码
//%d将该补码看作是有符号的整数:
//10000000 00000000 00000000 00000001 - -1的原码
//
//c为无符号的char,无符号数进行整型提升的时候,高位直接补0
//00000000 00000000 00000000 11111111 - 补码
//%d将该补码看作是有符号的整数:
//00000000 00000000 00000000 11111111 - 255的原码
//
return 0;
}
解释说明:详解见上述代码注释内容。
3.4.2 练习二
代码示例:
#include<stdio.h>
int main()
{
//练习二
char a = -128;
//10000000 00000000 00000000 10000000 - -128的原码
//11111111 11111111 11111111 01111111 - -128的反码
//11111111 11111111 11111111 10000000 - -128的补码
//存储时发生截断,截取整数-128补码的低八位:
//10000000
//
printf("%u\n", a);//4294967168
//%u 以十进制形式打印无符号的整数
//对于非整型的变量a来说,此时发生了整型提升
//
//a为有符号的char,在整型提升的时候,高位补充符号位,此处符号位为1
//11111111 11111111 11111111 10000000 - 补码
//%u将该补码看作是无符号的整数:
//11111111 11111111 11111111 10000000 - 4294967168的原码
//
return 0;
}
解释说明:详解见上述代码注释内容。
3.4.3 练习三
代码示例:
#include<stdio.h>
int main()
{
//练习三
char a = 128;
//00000000 00000000 00000000 10000000 - 128的补码(原码)
//存储时发生截断,截取整数128补码的低八位:
//10000000
//
printf("%u\n", a);//4294967168
//%u 以十进制形式打印无符号的整数
//对于非整型的变量a来说,此时发生了整型提升
//
//a为有符号的char,在整型提升的时候,高位补充符号位,此处符号位为1
//11111111 11111111 11111111 10000000 - 补码
//%u将该补码看作是无符号的整数:
//11111111 11111111 11111111 10000000 - 4294967168的原码
//
return 0;
}
解释说明:详解见上述代码注释内容。
3.4.4 练习四
代码示例:
#include<stdio.h>
int main()
{
//练习四
int i = -20;
//10000000 00000000 00000000 00010100 - -20的原码
//11111111 11111111 11111111 11101011 - -20的反码
//11111111 11111111 11111111 11101100 - -20的补码
//
unsigned int j = 10;
//00000000 00000000 00000000 00001010 - 10的补码(原码)
//
printf("%d\n", i + j);//10
//11111111 11111111 11111111 11101100 - i的补码
//00000000 00000000 00000000 00001010 - j的补码
//11111111 11111111 11111111 11110110 - i+j的补码
//
//11111111 11111111 11111111 11110101 - i+j的反码
//10000000 00000000 00000000 00001010 - i+j的原码 -> -10
//i+j -> -10
//
return 0;
}
解释说明:详情见上述代码注释内容。
3.4.5 练习五
代码示例:
#include<stdio.h>
#include<windows.h>
int main()
{
//练习五
unsigned int i;
for (i = 9; i >= 0; i--)
{
//循环将无限执行
printf("%u\n", i);//9 8 7 6 5 4 3 2 1 0 4294967295 ... 0 ...
Sleep(1000);//休眠1000毫秒,停顿显示输出的值
//for循环定义错误:“unsigned int”值始终介于“0”到“4294967295”范围之间.
}
//i--的值:9 8 7 6 5 4 3 2 1 0 ...
//
//00000000 00000000 00000000 00001001 - 9的补码(原码)
//...
//00000000 00000000 00000000 00000000 - 0的补码(原码)
//
//10000000 00000000 00000000 00000001 - -1的原码
//11111111 11111111 11111111 11111110 - -1的反码
//11111111 11111111 11111111 11111111 - -1的补码
//
//-1以补码形式存储到无符号整数i时,会被看作是无符号数(非负数)
//11111111 11111111 11111111 11111111 - 4294967295的补码
//所以循环控制变量i会始终大于等于0,条件判断恒为真,程序会陷入死循环
//
return;
}
解释说明:
1 整数-1的补码为32个1,存储到无符号整型变量中表示最大范围的那个数。
2 循环控制变量 i
始终大于等于0,条件判断恒为真,程序陷入了死循环。
3.4.6 练习六
代码示例:
#include<stdio.h>
#include<string.h>
int main()
{
//练习六
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d ", strlen(a));//255
//i: 0 1 ... 127 128 129 ... 255
//a[i]: -1 -2 ... -128 -129(127) -130(126) ... -256(0)
//
//10000000 00000000 00000000 10000000 - -128的原码
//11111111 11111111 11111111 01111111 - -128的反码
//11111111 11111111 11111111 00000000 - -128的补码
//
//整数-129存放到char数组元素时会发生截断
//截取整数-129补码的低八位:
//01111111
//01111111 - 127的补码,存储在char数组中
//
//当i增长到255,-1-i就变成了-256
//10000000 00000000 00000001 00000000 - -256的原码
//11111111 11111111 11111110 11111111 - -256的反码
//11111111 11111111 11111111 00000000 - -256的补码
//
//整数-256存放到char数组元素时会发生截断
//截取整数-256补码的低八位:
//00000000
//00000000 - 0的补码,存储在char数组中
//
//strlen是用来求字符串长度的,统计的是\0之前出现的字符个数,但不统计\0
//\0的ASCII码值为0,那么只需找到数组中元素为0的位置就行
//
//在该数组中,0之前的数组元素有255个:
//-1 ~ -128 127 ~ 1 ,一共128+127=255个元素
//strlen(a) -> 255
//
return 0;
}
解释说明:
-1 -2 ... -128 127 126 ... 2 1 0
3.4.7 练习七
代码示例:
#include<stdio.h>
unsigned char i = 0;
int main()
{
//练习七
for (i = 0; i <= 255; i++)
{
printf("hello world!\n");//死循环打印
}
//i增长到256时:
//00000000 00000000 00000001 00000000 - 256的补码(原码)
//整数256存放到i比那辆时会发生截断
//截取整数256补码的低八位:
//00000000
//
//i <= 255,进行关系运算时i发生整型提升
//i为无符号的char,在整型提升的时候,高位直接补充0:
//00000000 00000000 00000000 00000000 - 0的补码
//判断为真,循环执行
//
//i的值:0~255
//判断恒为真,陷入了死循环
//
return 0;
}
解释说明:
1 当 i++
的值增长到256时,存储256时会发生截断,此时 i
实际存储的是0;
2 i
接着增长变化,永远都满足 i<=255
这个条件,那么程序会陷入死循环。
三、浮点型在内存中的存储
浮点数与整数在内存中的存储是不同的。
1 常见的浮点数
1.1 介绍
示例:
3.1415926
1E10
浮点数家族:
float
double
long double
说明:浮点数表示的范围在 <float.h>
中定义。
1.2 示例1
浮点数在内存中的存储与取用。
代码示例:
#include<stdio.h>
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n = %d\n", n);//9
printf("*pFloat = %f\n", *pFloat);///0.000000 //整型存储的数据,以浮点数形式取出
*pFloat = 9.0;
printf("n = %d\n", n);///1091567616 //浮点数存储的数据,以整型形式取出
printf("*pFloat = %f\n", *pFloat);//9.000000
return 0;
}
解释说明:
1 浮点数在内存中的存储方式和取出方式与整型数据是不同的。
2 浮点数看待内存中的数据以及以浮点数形式存储的数据和整型是不同的。
2 浮点数的存储规则
n
和 *pFloat
在内存中明明是同一个数( 管理同一段内存空间 ),为什么浮点数和整数的解读结果会差别这么大?
想要明白和理解不同的解读结果,需要搞懂浮点数在计算机内部的表示方法。
2.1 解读
根据国际标准 IEEE-754
(电气和电子工程协会),任意一个二进制浮点数 V
可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S //表示符号位;当S=0时,浮点数V为正数;当S=1时,V为负数
M //表示有效数字,大于等于1且小于2
2^E //表示指数位
说明:
1 十进制的 5.0
,用二进制表示为: 101.0
,相当于 1.01 * 2^2
。
那么按照上述浮点数 V
的表示格式,可以得出:S=0
、M=1.01
、E=2
。
2 十进制的 -5.0
,用二进制形式为: -101.0
,相当于 -1.01 * 2^2
。
那么可以得到:S=1
、M=1.01
、E=2
。
3 IEEE 754 规定:
对于32位的浮点数,最高的1位表示符号位 S
,接着的8位是指数位 E
,剩下的23位表示有效数字 M
。
2.2 浮点数的存储
2.2.1 单精度和双精度浮点数的存储
图示说明:单精度浮点数
图示说明:双精度浮点数
2.2.2 有效数字 M
和指数 E
的存储规定
IEEE-754
对有效数字M
和指数E
还有一些特别规定。
① 有效数字 M
前文叙述过,1 <= M < 2
,也就是说有效数字 M
可以写成 1.xxxxxx
的形式,其中的 xxxxxx
表示小数部分。
IEEE 754
规定,在计算机内部保存 M
时,默认整数位为1,因此可以被舍去,只保存后面的小数 xxxxxx
部分。
比如保存 1.01
时,只保存小数部分 01
,等到读取该数的时候,又把第一位的1加上去。这样做的目的是可以节省 1bit
的空间,能增加1位有效数字。
以32位浮点数为例,留给有效数字 M
的空间只有 23bit
,将第一位的1舍去以后,就相当于可以保存24位有效数字(1个整数位,23个小数位)。
② 指数 E
至于指数 E
,情况就比较复杂。首先,指数 E
是一个无符号整数( unsigned int
)。这意味着,如果 E
为8位,它的取值范围就是 0~255
;如果 E
为11位,它的取值范围就是 0~2047
。
但是我们知道,科学计数法中的 E
是可以出现负数的;所以 IEEE 754
还规定,存入内存前,指数 E
的真实值必须再加上一个中间数;对于8为的 E
,这个中间数是127;对于11位的 E
,这个中间数是1023。
比如,2^10
的指数 E
是10,所以在保存32位浮点数时,存入的指数 E
为 10+127=137
,即二进制 10001001
。
2.2.3 有效数字 M
和指数 E
的取出规定
指数
E
从内存中取出还可能再分为以下三种情况:
① 指数 E
不全为0或不全为1(存储值)
此时浮点数就采用如下的规则表示,指数 E
的计算值(存储值)减去127(或1023),得到实际值,再将有效数字 M
前面第一位加上1。
比如:
整数 0.5 (1/2)
的二进制形式为 0.1
,由于规定整数位必须为1,那么就将小数点右移1位,表示为:1.0 * 2^(-1)
;
其阶码(指数 E
存入的值)为 -1+127=126
,二进制形式表示为:01111110
;尾数(有效数字 M
的值)1.0
去掉整数部分 1
后剩余的小数部分为 0
,补齐0到23位:00000000000000000000000
。
整数 0.5 (1/2)
在内存中存储的二进制形式为:
0 01111110 00000000000000000000000
// S E M
② 指数 E
全为0(存储值)
此时浮点数的**指数 E
的真实值为 1-127
或者 1-1023
**(-126或-1022)。
有效数字 M
的整数位不再加上第一位的1,而是还原为 0.xxxxxx
的小数形式。这样做是为了表示 +-0
,以及接近于0值的很小数字。
③ 指数 E
全为1(存储值)
此时如果有效数字 M
全为0,则表示 +-无穷大
(正负取决于符号位 S
)。
3 示例1的解读(1.2 示例1)
浮点数在内存中的存储与取用。
代码示例:
#include<stdio.h>
int main()
{
int n = 9;
//00000000 00000000 00000000 00001001 -- 9的补码
//
//pFloat指向n的地址,但是是以浮点数的形式看待该内存的数据
float* pFloat = (float*)&n;
//看待内存数据的形式:
//0 00000000 00000000000000000001001 -- *pFloat看待的数据
//S E M
//
//S = 0
//阶码存储时全为0,E的实际值为:1-127=-126
//M = 0.0000000 00000000 00001001
//(-1)^0 * 0.0000000000000000001001 * 2^(-126)
//
printf("n = %d\n", n);//9
printf("*pFloat = %f\n", *pFloat);///0.000000 //整型存储的数据,以浮点数形式取出
*pFloat = 9.0;
//9.0(10) --> 1001.0(2) --> 1.001 * 2^3
//S = 0 --> 0
//E = 3 --> 00000011 + 01111111 = 10000010 //(3+127=130)
//M = 1.001 --> 001 00000000000000000000 //1.XXXXXXXX
//浮点数的存储:
//S E M
//0 10000010 00100000000000000000000
//0100 0001 0001 0000 0000 0000 0000 0000 - 二进制
//4 1 1 0 0 0 0 0 - 十六进制
//
//二进制补码的整型取出:
//0X 41 10 00 00 -- 1091567616 //十六进制 -> 十进制
//
printf("n = %d\n", n);//1091567616 //浮点数存储的数据,以整型形式取出
printf("*pFloat = %f\n", *pFloat);//9.000000
return 0;
}
解释说明:
1.将整数9也就是 0x00000009
按浮点数形式取出的结果是 0.000000
,这是为什么呢?
首先,将整数9的二进制进行拆分:
0 00000000 00000000000000000001001
//S E M
得到符号位 S
为 0
;阶码(指数 E
的存储值)全为0,实际 E
为 1-127=-126
;有效数字 M
为 0.00000000000000000001001
。
浮点数 V
就写成:
V = (-1)^0 * 0.00000000000000000001001 * 2^(-126) = 1.001 * 2^(-146)
很显然,V
是一个很接近0的很小的数,所以用十进制小数表示为 0.000000
。
2.将以浮点数形式存储的 9.0
用二进制形式表示的是什么?以整数形式取出的结果是什么?
首先,浮点数 9.0
的二进制表示形式为:1001.0
,即 1.001 * 2^3
。
9.0 -> 1001.0 -> 1.001 * 2^3 -> (-1)^0 * 1.001 * 2^3
//S = 0
//E = 3
//M = 1.001
那么第一位的符号位 S
的存储值就为 0
;指数 E
的存储值就为 3+127=130
,也就是 100000010
;
有效数字 M
的存储值就为 001
,同时后面再添上20个0: 00100000000000000000000
。
所以,浮点数 9.0
写成二进制形式就为:
0 10000010 001 00000000000000000000
//
0100 0001 00010000 0000 0000 0000 0000
4 1 1 0 0 0 0 0
//0X 41 10 00 00
将该二进制以整数形式取出的结果为:0X41100000
,也就是 1091567616
。
总结:
本章介绍了整数与浮点数在内存中的存储规则;讲解了整数的原码、反码、补码的计算,浮点数的存储与取出规则;给出了大小端存储以及如何判断大小端存储的笔试题例。
感谢您的阅读!如有任何错误,欢迎您的批评指正!