1.大小端字节序
1.1 大小端字节序的概念
大端模式:将数据的低位字节内容保存到内存的高地址处,数据的高位字节内容保存到内存的低地址处;
小端模式:将数据的低位字节内容保存到内存的低地址处,数据的高位字节内容保存到内存的高地址处;
例: 一个16bit的short型x,在内存中的地址是0x0010,x的值是0x1122。那么 ,0x11是高字节,0x22是低字节。如果是大端模式,那么 0x11在低地址处,即0x0010中,0x22放在高地址处,即0x0011中。
1.2 为什么会有大小端字节序
(1)方便处理多字节的数据类型;
(2) 对于位数大于8bit的处理器(如 16位处理器 或者 32位处理器)由于寄存器的宽度大于1个字节,那么大小端字节序被用来处理存放占有多个字节的数据的问题。
>> 判断当前机器的大小端字节序
#include <stdio.h>
int main()
{
int i = 0;
int p = *(char*)&i;
if (p)
printf("大端\n");
else
printf("小端\n");
return 0;
}
2. 整数在内存中的存储
2.1 理论简述
对于整形数据,数据存放在内存中的其实是补码
为什么 ?
(1)在计算机系统中,数值一律使用补码表示和存储。因为,使用补码可以将符号位和数值域统一处理;
(2) 加法减法也可以统一处理(CPU只有加法器);
(3)补码和原码相互转换,运算过程相同,不需要额外的硬件电路;
【知识回顾 : 原码,反码,补码】
整数有三种二进制表示方式:原码,反码,补码
这三种表示方式均有符号位和数值位,符号位都是用 0 表示 “正”,用 1 表示 “负”;
数值位最高位的一位被当成符号位,其余都是数值位;
正整数的原码、反码 、补码都相同。
负整数的原码、反码、补码各不相同:
原码:直接将数值按照正负数形式翻译成二进制得到;
反码:原码符号位不变,其他位按位取反;
补码:反码 + 1;
原码 ——> 补码 : 取反 ,+ 1; 补码 —— > 原码 : 取反 ,+ 1;
>> 2.2 经典题目
题目1
#include <stdio.h>
int main()
{
char a = -1;
signed char b = -1; //char 是 signed char 还是 unsigned char 并不确定,这取决于编译器;
// 在VS编译器里,char 等同于 signed char
unsigned char c = 1;
printf("a = %d ,b = %d ,c = %d",a, b, c); // %d 十进制打印有符号整型
return 0;
}
运行结果 :
为什么是这个结果呢?
在内存中,-1是按照补码存储的;由于char只占1个字节,所以只会留下最后8个bit的数字;a 截断之后,进行整型提升(由于最高位是1,所以之前的位全部补1,直至填满32位),此时的结果仍然是补码,再转换成原码; c 同理,只是进行整型提升的时候最高位是 0,所以全部补0;
>> 题目2
比较两个代码 两者的结果是否不同?
#include <stdio.h>
int main()
{
char a = -128; // 有符号char 取值范围 -128~127
printf("%u\n", a); // %u 打印无符号整型
return 0;
}
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
两者结果均为 : (本题原理类似题目1 )
>> 题目3
#include <stdio.h>
int main()
{
char arr[1000];
for (int i = 0; i < 1000; i++)
{
arr[i] = -1 - i;
}
printf("%d", strlen(arr));
return 0;
}
猜猜运行结果是多少 ?😛 😝
当当!!
char 的取值范围是 -128 ~ 127
可以用下面这张图来解释结果
数据在内存中都是以补码形式存放,由于char只占8个bit位,所以只有最后8 bit位的数据保留。只有最后8bit位的数据进行加减,-128 - 1的结果变成127。如此循环往复,周而复始。128 + 127 = 255
>> 题目4
比较两个代码 它们的结果分别是什么呢?
// 1
#include <stdio.h>
unsigned int i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello c\n");
}
return 0;
}
// 2
#include <stdio.h>
int main()
{
unsigned int i = 0;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
两个代码都会陷入死循环
对于代码1 ,unsigned int 的取值范围是 0~ 255, 所以for 循环里面的限制条件 i <= 255 对于任意一个 i 都是恒成立 ,所以循环永远不会停止; 对于代码2,同理,i >= 0 恒成立 ,所以程序也会无限循环下去;
>>>> 那么浮点数和整数在内存中的存储方式一样吗? <<<<
#include <stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;
printf("n = %d\n", n);
printf("*p = %f\n", *p);
*p = 9.0;
printf("n = %d\n", n);
printf("*p = %f\n", *p);
return 0;
}
运行结果 :
由此看出 浮点数和整数在内存中的储存方式不同
3. 浮点数在内存中的存储
3.1 简述
根据国际标准 IEEE (电气和电子工程协会)754,任意一个二进制浮点数 V 都可以表示成下面的形式:
V = ( -1 )^S * M * 2^E;
其中,(-1)^S 表示符号位,当 S = 0, V 为正数;当 S =1 时,V 为负数;
M 表示有效数字,1 <= M < 2;
2 ^E 表示指数位;
例: 十进制的5.0 ,写成二进制是101.0 ,相当于 1.01 * 2^2
此时,S = 0; M = 1.01; E = 2;
IEEE 754规定:
对于32位浮点数,最高一位存储符号位S,接着的8位存储指数E,剩余的23位存储有效数字M;
对于64位浮点数,最高的一位存储符号位S,接着的11位存储指数E,剩余的52位存储有效数字M
3.2 浮点数存的过程
M :
由于 1 <= M < 2, 所以M可以写成 1.xxxxxxx 的形式 (其中,xxxxxx表示小数部分)
IEEE 754 规定,计算机内部保存M 时,默认这个数字的第一位总是1,因此1可以被舍弃,只保存后面的小数部分;
E :
E 是无符号整数 ,然而在科学计数法里面,E可以是负数。所以IEE 754 规定,存入内存时,E的真实值必须再加上一个中间数。如果 E 是8位,中间数是127;如果 E 是11 位,中间数是1023。
3.3 浮点数取的过程
(1) E 不全为0或1(有1有0)
指数 E的计算值减去127(或 1023),得到真实值,再将有效数字M前面加上第一位的1
(2) E全为0
此时E = 1-127 (或 1-1023),有效数字不再加上第一位的1,而是还原成 0.xxxxxx的小数。这是表示 +0 或 -0 或接近0的很小的数字。 (打印结果一般为0.000000,因为2^127非常大)
(3)E全为1
此时若有效数字M全为0,表示无穷大(或 负无穷大)