一、整型数据在内存中存储
在学习计算机基础时,就接触过整型的二进制表示:原码,反码,补码
对于有符号的整数,这三种表示方式的有符号位和数值位,符号位用0表示正,用1表示负,用二进制最高位来表示符号位,其他都是数值位。
对于正整数来说:原码,反码和补码都相同
负整数三种表示方法各不相同
原码:直接将整数按照正负转换为二进制得到的就是原码
反码:将原码的符号位不变,其他位依次按位取反就得到反码
补码:反码+1就是补码
反码与补码之间的转换就是,取反加一
对于整型数据来说:数据就是以二进制补码的形式存放在内存中
在计算机系统中,数值一律用补码来存储和表示。原因就在于,使用补码可以将符号位与数值位统一处理。
同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
二、大小端字节序
第一次看到这个大小端字节序,可能感到很疑惑,具体什么是大小端字节序呢?
这里,我们写代码调试来看一下:
int main(){ int a = 0x11223344; return 0; }
这里我们可以看到a当中0x11223344这个数字是以字节为单位,倒着存储的,这又是为什么呢?
什么是大小端?
我们知道,在内存中存储数据一般是以字节为单位的,而当超过一个字节大小的数据,存储的过程中就要遇到顺序问题,所以,内存中存储数据是有一定顺序的,按照不同的存储顺序,就分为大端字节序存储和小端字节序存储,具体概念如下:
大端字节序存储:
是指数据的 低位字节 内容保存在 内存的高地址 处,而数据的 高 位字节内容,保存在内存的 低 地址处。
小端字节序存储:
是指数据的 低位字节 内容保存在 内存的低地址 处,而数据的 高 位字节内容,保存在内存的 高 地址处。
为什么要有大小端字节序呢?
这是因为在计算机系统中,是以字节为单位的,每个地址单元都对应一个字节,一个字节是8个bit位,但是在计算机中,有8bit的char型,有16bit的short类型,还有32个bit的long型(具体大小取决于编译器);此外,对于那些位数大于8位的处理器,如16或者32位处理器(还有64位等),它们的寄存器宽度都要大于一个字节,这样就必然存在着如何处理多个字节安排这一个问题。因此就有了大端存储模式和小端存储模式。
我们了解了大小端字节序存储这个东西,接下来就写代码来判断一下VS编译器是大端存储模式还是小端存储模式呢?
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; }
这里 *(char*)&i将整型指针强制类型转化成char*再解引用就只访问一个字节,(访问的就是地址低的那一个字节),这样如果访问到的是1,就说明将int将低位字节存储到低地址处,存储方式就是小段存储方式;如果访问到的不是1,就说明存储方式是大端存储方式。
当然,也有其他的方式来判断:
int check_sys() { union { int i; char c; }un; un.i = 1; return un.c; } int main() { int ret = check_sys(); if (ret == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
这里用到了自定义类型的联合体这里简单说一下联合体(后面会详细讲解):
联合体也叫做共用体,联合体可以由多个成员不同类型构成,联合体特点就是所以的成员共用一块内存,(编译器只会给最大的成员分配足够的内存),给联合体的其中一个成员赋值,其他成员的值也会发生变化。
这里也是,给int类型的i进行赋值,然后用char类型去访问,只访问一个字节。
三、试题练习
接下来,看一些例题,来加强一下理解
练习1、
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; }
看到这里,应该要知道一个问题,字符char类型在内存中占一个字节,在内存中也是以二进制的方式存储的,例如a,b等这些存储的其实是对应的ASSIC码值。
接下来,看一下代码,char a = -1;char一个字节,8个bit位,整型存储的是二进制,所以将-1转化位二进制就是 :
10000001 ——原码
11111110 ——反码
11111111 ——补码
将补码存储到a当中
补充:char默认就是有符号型(signed char)
这里signed char与char存储方式一样
再看 unsigned char c = -1;这里还是将-1转化位二进制
11111111 ——补码
将补码存储到c当中去,而c是无符号类型,它就会把符号位当成数值位来看待;
接下来以%d的形式输出,由于char只占一个字节,这里就会涉及到整型提升将char类型提升至int型:
整型提升知识补充:
整型提升的规则:
整型提升分为有符号和无符号两种,对于有符号的:整型提升时是按照变量的补码被截断时的最高位(符号位)是什么进行补位的,如果截断后最高位即最左面的一位数(符号位)为 1 则在最高位前补 1 ,如果最高位(符号位)是 0 则在前面补 0 ,补够32位即int类型即可。 无符号的: 直接在被截断的前面补 0 即可。
简单了解一下整型提升,再来接着看代码
以%d的形式输出,首先就要先整型提升
11111111 ——a
11111111 11111111 11111111 11111111 —— (补码)整型提升以后的a
10000000 00000000 00000000 00000000 —— 反码
10000000 00000000 00000000 00000001 —— 原码
然后进行输出,因为是%d是以有符号整型输出,所以这里输出的就是 -1;
b与a一样,都位有符号类型。
再看无符号型的c,
11111111 —— c
00000000 00000000 00000000 11111111 —— (补码)整型提升以后的c
00000000 00000000 00000000 11111111 —— 反码
00000000 00000000 00000000 11111111 ——原码
因为c是无符号类型(即是正数)原反补码都相同
然后以%d形式进行输出,输出结果就是255。
练习2、
#include <stdio.h> int main() { char a = -128; printf("%u\n",a); return 0; }
char 有符号类型 -128转化位二进制
10000000 —— 原码
10000000 —— 反码
10000000 —— 补码
a里存储的就是-128的补码;
然后以%u(无符号整型)输出,首先还是要整型提升 -128是有符号类型
10000001 —— a
11111111 11111111 11111111 10000000 —— 整型提升后
因为是以有符号类型输出,所以编译器就会将整型提升后的符号位当作数值来看待,直接将其转化成十进制,然后输出,结果就是4294967168
练习3、
#include <stdio.h> int main() { char a = 128; printf("%u\n",a); return 0; }
练习2中是将-128存储,现在来存储128然后以%u形式输出。
这里 char类型取值范围 -128 —127
128存储到char类型中,可能会出现数据丢失的现象
这里 10000000 —— a
然后整型提升,符号位是0
00000000 00000000 00000000 10000000 —— 整型提升后(补码)
11111111 11111111 11111111 01111111 —— 反码
11111111 11111111 11111111 10000000 —— 原码
以%u形式输出,将符号位当成数值位来看待,直接转化成十进制数然后输出,结果就是4294967168
练习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; }
在第一眼看到这个代码时,第一感觉可能会跟我一样,一看循环进行赋值了1000,那用strlen来求字符串长度,那不就是1000吗?
在讲述这一个练习之前先补充一点知识
知识补充:
我们知道,char类型只占用一个字节,也就是8个bit位,那再存储数据的时候,它所存储的数据就是有限制的
对于有符号的char类型
图中我们可以看到,127再加一后 0111 1111 就变成了 1000 0000;
而1111 1111加一就变成了 0000 0000
对于无符号类型的char就简单了
这里 1111 1111加一就变成了 0000 0000。
知道了char类型的取值范围再看这个练习题
a[i] = -1 - i ;
i从零开始,a[0]就等于-1,这样一直赋值下去,当 (-1 - i )等于-128 时,i++;这时表达式的值就变成了127,再接着进行赋值,这个表达式的值总会变成0,而我们又知道strlen遇到'\0'停止,'\0'的ASSIC码对应的值就时0,所以,strlen遇到a这个数组中的0时,就停止计数了,所以这里输出的结果是255。
练习5、
在有了以上对char类型数据的理解,看以下两个题就容易多了
#include <stdio.h> unsigned char i = 0; int main() { for(i = 0;i<=255;i++) { printf("hello world\n"); } return 0; }
这个代码会一直循环输出 hello world , unsignned char取值范围是 0 -- 255, 255再加一就变成0,循环会一直运行,如果使用VS编译器,代码会有错误它提示
int main() { unsigned int i; for(i = 9; i >= 0; i--) { printf("%u\n",i); } return 0; }
这个也是同理,unsigned char 类型 0再减去1 就变成255,循环一直进行下去
四、浮点型数据在内存中存储
了解了整型数据在内存中的存储,接下来,来了解浮点型数据在内存中的存储:
浮点型数据的存储,根据国际标准IEEE(电器和电子工程协会)754,任意一个浮点数V都可以表示成一下形式:
- 这里-1的S次方表示符号位,S = 0,V就是正数;S=1,V就是负数
- M表示有效数字,M是大于等于1,小于等于2的
- 2的E次方表示指数位
举例:
十进制的5.0,写成二进制 101.0 ,相当于1.01*2^2
用以上格式表示就是 S=0,M=1.01,E=2。
浮点型数据的存储
对于32位的浮点型数据,最高位存符号位S,接着8位存指数E,剩下的23位存储有效数字M。
而对于64位的浮点型数据,最高位存符号位S,接着11位存指数E,剩下的的52位存有效数字M。
知道是怎样存进内存的,那也要知道在存储过程中,存储的规定
IEEE 754 对于有效数字M和指数E,还有一些规定
对于有效数字E:
有效数字 1<=M<2,也就是M可以写成 1.xxxxx的形式,其中xxxxx是小数部分。
IEEE 754 规定,在计算机内存保存M时,默认这个数的第一位总是1,所以可以将其舍去,只保存后面的小鼠部分;
比如,在保存1.01时,只保存01,在读取的时候,再把第一位的1加上去。这样做,是省略1位有效数字。比如,32位浮点数,留给M只有23位 ,舍去第一位的1,就相当于可以保存24位有效数字
对于指数E,存储的情况就比较复杂
首先,E是一个无符号整数,这就说明,如果E是8位,它的取值就是 0-255;如果E是11位,取值就是0-2047.
但是,我们知道指数是存在负数的,所以IEEE 754 就规定了在存储时E的真实值必须加上一个中间数,(8位的E,中间数是127; 11位的E,这个中间数就是1023),比如 2^10的E是10 ,保存成32位浮点数,在存储时就必须保存成 10+127 = 137,就是 1000 1001。
浮点型数据的数取
大致可以分为三种情况:
1、E不全为0或不全为1
这时,浮点数采用下面规则表示,(E的计算值减去127或1023),得到真实值,在将有效数字M前加上第一位的1.
比如:0.5二进制是0.1,根据规定正数部分必须是1,所以就表示位 1.0^2*(-1),其阶码(E的存储值)是 -1+127 = 126,表示为 0111 1110,位数1.0 去掉整数部分为0,补齐23位 ,所以二进制表示形式:
0 01111110 00000000000000000000000
2、E全为0
这时,浮点型的指数E就是1-127(或者1-1023)即为真实值,有效数字M不在加上第一位的1,而是还原为0.xxxxxx的小数。(这是为了表示+/- 0,和接近于0的很小的数字)
3、E全为1
这时,如果有效数字M全为0,就表示+/- 无穷大(正负取决于符号位s)
了解了这些,就看一段代码:
#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); return 0; }
在之前看到这样的运行结果,可能会感到很懵,在我们知道了浮点型数据的存储以后,来看这样的代码:
首先第一个 printf("n的值为:%d\n",n); 这个应该都可以理解,n是整型数据,以%d形式输出,就是9;
然后来看 printf("*pFloat的值为:%f\n", *pFloat);
9 以整型的形式存储在内存中,二进制如下:
0000 0000 0000 0000 0000 0000 0000 1001
要以%f 形式输出,那就会把这个二进制序列按照浮点型数据二进制来处理,
符号位S = 0 ,后面8位即指数位 E = 0000 0000,
最后的23位就是有效数字 M= 000 0000 0000 0000 0000 1001。
这样指数E全为0,就按照指数E全为0 的方式,
浮点数 V= (-1)^0 *000 0000 0000 0000 0000 1001 *2^(-126);
这样,V显然就是应该很小的接近于0的正数,用十进制小数表示就是0.000000
紧接着来看 *ploat = 9.0 以后,在以%d形式输出:
这里就要按照浮点型数据存储将9.0存储到内存中,
9 的二进制 1001.0 换成科学计数法就是 1.001 * 2^3
所以S = 0,E = 3 + 127, M等于001后面追加20个0 , 写成二进制就是
0 10000010 001 0000 0000 0000 0000
这个二进制数,被当作正数来解析时,就被当中内存中的补码,原码 转换为十进制就是 1091567616
最后以%f的形式在输出以浮点型存储到内存中的9.0,输出结果就是 9.000000。
感谢观看,希望一下内容对你有所帮助,如果内容对你有作用,可以一键三连加关注,作者也正在学习中,有错误的地方还请指出,感谢!!!