👉 欢迎阅读这篇文章 👇
目录
- 1、整数在内存中的存储
- [2、 大小端字节序和字节序判断](#2、 大小端字节序和字节序判断)
- 3、浮点数的存储
-
- 3.1浮点数的存储
-
- 3.1.1浮点数存的过程
- [3.1 2浮点数取的过程](#3.1 2浮点数取的过程)
- 3.2练习
1、整数在内存中的存储
整数的2进制表示方法有三种:原码、反码和补码。
有符号的整数,三种表示方法均有符号位 和数值位两部分,符号位用0表示正,用1表示负,最⾼位的⼀位是被当做符号位,剩余的都是数值位。
正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同。
- 原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
- 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
- 补码:反码+1就得到补码。
对于整形来说:数据存放内存中其实存放的是⼆进制的补码。
补充:对于一个数值,会有前缀和后缀修饰
- 前缀:表示这个数值的进制类型(
0b二进制、0x十六进制、0八进制)- 后缀:表示这个数值的类型(signed int、unsigned int...)
我们平时见到最多的就是既没有前缀又没有后缀的数值,这时就默认代表十进制的有符号整数(int、long、long)。
2、 大小端字节序和字节序判断
我们将整型数据存放在内存后,可以观察到一个细节:

我们把0x11223344存到a里,发现a的地址中是以44、33、22、11的顺序存储的,这就是小端字节序存储。
2.1大小端
超过⼀个字节的数据在内存中存储的时候,就会有存储顺序的问题,按照不同的存储顺序,我们分为**⼤端字节序存储** 和**⼩端字节序存储**。
- 大端存储模式:是指数据的低位字节内容保存在内存的高地址处。而数据的高位字节内容保存在内存的低地址处。
- 小端存储模式:是指数据的低位字节内容保存在内存的内存的低地址处。而数据的高位字节内容保存在内存的高地址处。
2.2练习
2.3.1练习1
设计⼀个⼩程序来判断当前机器的字节序。
c
//设计⼀个⼩程序来判断当前机器的字节序。
int main()
{
int a = 1;//访问a的地址中第一个字节的值,看是否是1
if (*(char*) & a)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
2.3.2练习2
c
#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;
}
c
#include <stdio.h>
int main()
{
char a = -1;
//-1是有符号整数,是负整数
//-1的原码:10000000 00000000 00000000 00000001
//-1的补码:11111111 11111111 11111111 11111111
//a是char类型的
//存到a里的:11111111
signed char b = -1;
//-1是有符号整数,是负整数
//-1的原码:10000000 00000000 00000000 00000001
//-1的补码:11111111 11111111 11111111 11111111
//b是 signed char类型的
//存到b里的:11111111
unsigned char c = -1;
//-1是有符号整数,是负整数
//-1的原码:10000000 00000000 00000000 00000001
//-1的补码:11111111 11111111 11111111 11111111
//c是 unsigned char类型的
//存到c里的:11111111
printf("a = %d, b = %d, c = %d", a, b, c);
//a
//%d:以十进制的形式打印有符号整数。
//a要传进printf函数里:
//整型提升:(a是char类型的,假设为有符号类型,补符号位)
//11111111 11111111 11111111 11111111(以%d有符号规则解读,这个补码对应的数值,就是原码表示的-1)
//原码:10000000 00000000 00000000 00000001 (十进制:-1)
//b
//%d:以十进制的形式打印有符号整数。
//b要传进printf函数里:
//整型提升:(b是signed char类型的,有符号类型,补符号位)
//11111111 11111111 11111111 11111111(以%d的有符号规则解读,这个补码对应的数值,就是原码表示的-1)
//原码:10000000 00000000 00000000 00000001 (十进制:-1)
//c
//%d:以十进制的形式打印有符号整数。
//c要传进printf函数里:
//整型提升:(c是unsigned char类型的,无符号类型,补0)
//00000000 00000000 00000000 11111111(以%d有符号规则解读,这个补码对应的数值,就原码表示的255)
//原码:00000000 00000000 00000000 11111111 (十进制:255)
return 0;
}

2.3.3练习3
(1)
c
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
c
#include <stdio.h>
int main()
{
char a = -128;
//-128 有符号整数 负整数
//原码:10000000 00000000 00000000 10000000
//补码:11111111 11111111 11111111 10000000
//a是char类型的
//存到a里的:10000000
printf("%u\n", a);
//a要传进printf里
//char类型比int小-整型提升
//a是char类型的-假设是有符号类型的-补符号位(1)
//11111111 11111111 11111111 10000000(以%u无符号规则解读,都是数值位,根据权重解读为:4,294,967,168)
return 0;
}
(2)
c
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
c
#include <stdio.h>
int main()
{
char a = 128;
//128 类型:有符号整数 正整数
//原码:00000000 00000000 00000000 10000000
//补码:00000000 00000000 00000000 10000000
//a是char类型的
////存到a里的:10000000
printf("%u\n", a);
//a要传进printf里
//char类型比int小-整型提升
//a是char类型的-假设是有符号类型的-补符号位(1)
//11111111 11111111 11111111 10000000(以%u无符号规则解读,都是数值位,根据权重解读为:4,294,967,168)
return 0;
}

2.3.4练习4
c
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%zu", strlen(a));
return 0;
}
这里是一个char类型的数组,数组里的每个元素都是char类型的,所以我们要先了解char类型的数值的一些内容
char/signed char

补码运算:

对char类型的数据运算,(二进制补码:10000000)十进制:-128 减去1就是(二进制补码:01111111)十进制:127,(二进制补码00000000)十进制:0减去1就是(二进制补码:11111111)十进制:-1。所以这就是一个循环的过程,从0到-128,然后127-0,然后继续0--128。

上方的代码,就是反映了这个循环过程
strlen函数的功能就是统计字符串中 '\0' 之前的字符的个数。在这里就是统计这个数组元素0之前有多少个元素。经过计算可得有255个。

2.3.5练习5
(1)
c
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
这里i是一个unsigned char的变量,经过上面的分析可得unsigned char的范围是0~255,而循环条件是i <= 255,会进入无限循环。无限打印hello world。
(2)
c
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
这里i是一个unsigned int的变量,unsigned int类型的值一定大于0,而循环条件是i >= 0,会进入无限循环。先从9打印到0,再从一特别大的的值打印到0,一直循环打印。
2.3.6 练习6
c
#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;
}
图解:

&a表示整个数组的地址,&a+1指向数组结束后的内存,强制类型转换为int*,赋给ptr1,ptr1[-1]为*(ptr1-1),即为4。%x,以十六进制形式打印仍是4。

a表示整个数组首元素的地址,a强制类型转换为int,就变成了一个整型值,+1,就是正常的加1,加完1后数值就等于((char*)a)+1,数值就是第一个元素内第二个字节的地址,再强制类型转换为int*后赋给ptr2,ptr2指向第一个元素内第二个字节的地址,解引用后,以%x的规则往后读取4个字节的内容,为0x02000000,打印为2000000

3、浮点数的存储
常见的浮点数有:3.14159、1E10等,浮点数家族包括:: float、double、long double 类型。
浮点数表⽰的范围: float.h 中定义。
3.1浮点数的存储
根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
V = ( − 1 ) s × M × 2 E V = (-1)^{s} \times M \times 2^{E} V=(−1)s×M×2E
- ( − 1 ) s (-1)^{s} (−1)s表示符号位,当S=0时,V为正数;当S=1时,V为负数。
- M表示有效数字,M是大于等于1小于2的。
- 2 E 2^{E} 2E表示指数位
举例:
十进制的5.0,写成二进制为101.0,相当于 1.01 ∗ 2 2 1.01*2^2 1.01∗22,这时S=0,M=1.01,E=2。
十进制的-5.0,写成二进制为-101.0,相当于 − 1.01 ∗ 2 2 -1.01*2^2 −1.01∗22,这时S=1,M=1.01,E=2。
IEEE 754规定:
- 对于32位的浮点数(float),最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M。

- 对于64位的浮点数(double),最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M。

3.1.1浮点数存的过程
IEEE 754 对有效数字M和指数E,还有⼀些特殊规定。
前面说过,1<=M<2,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表⽰⼩数部分。
IEEE 754 规定,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去 ,只保存后⾯的xxxxxx部分。
比如:保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。
这样样做的⽬的是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保存24位有效数字。
⾄于指数E,情况就⽐较复杂
首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,那么E的取值范围为0~255;如果E为11位,那么E的取值范围为0~2047。但是我们知道科学计数法时E可能出现会负数,比如:0.5,写成二进制为0.1,转成科学计数法是 1 ∗ 2 − 1 1*2^{-1} 1∗2−1,这时E就为负数。所以IEEE 754规定,存⼊内存时E的真实值必须再加上⼀个中间数 ,对于8位的E ,这个中间数是127 ;对于11位的E ,这个中间数是1023。
⽐如, 2 10 2^{10} 210 的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
这样的浮点数存储⽅式很巧妙,但是我们也要注意到有的浮点数是有可能⽆法精确保存的。⽐如:1.2。

通过调试可以看出,1.2在内存中并没有精准的存储。
3.1 2浮点数取的过程
指数E从内存中取出可以分成三种情况:
E不全为0或不全为1(常规情况)
这时,浮点数采用将指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上1。
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字。
E全为1
表示一个正负无穷大的数(取决于符号位s)
3.2练习
c
#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;
}
c
#include <stdio.h>
int main()
{
int n = 9;
//9存储在整型变量n中,是一个有符号整数,正整数
//00000000 00000000 00000000 00001001
float* pFloat = (float*)&n;
//将n的地址转为float*类型的存储在pFloat中,pFloat指向的就是一个浮点型数据
printf("n的值为:%d\n", n);//9
printf("*pFloat的值为:%f\n", *pFloat);
//pFloat指向的是一个浮点型数据,就会把00000000 00000000 00000000 00001001当作浮点型来解读
//s = 0 E位全0 一个确定的、极小的正数 所以⽤⼗进制⼩数表⽰会被四舍五入为就是0.000000。
*pFloat = 9.0;//将一个浮点型数据9.0赋给*pFloat,pFloat指向n
//9.0写成二进制就为1001.0即1.001*2^3
//s = 0 M = 001 E = 3(3+127 = 130)
//0 10000010 0010000000000000000000
printf("n的值为:%d\n", n);
//以%d的规则解读0 10000010 0010000000000000000000,这个补码表示的值,直接按照权重解读得到十进制数1,091,567,616
//01000001 00010000 00000000 00000000(十进制:1,091,567,616)
printf("*pFloat的值为:%f\n", *pFloat);//按照%f的规则,就正常打印浮点数9.000000
return 0;
}