目录
1.整数在内存中的存储
举例1
举例2
举例3
举例4
举例5
举例6
举例7
2.浮点数在内存中的存储
3.大小端模式
1.整数在内存中的存储
我们知道,计算机中存储数据用的是二进制,但是和直接将整数转换成二进制还不够,还要进行一定的变换,下面补充原码,反码和补码的概念
首先在这三种码中,对于有符号的整数来说存在一个符号位,存在于最高位,0为正1为负
原码:直接将正负数按照二进制规则翻译得到的二进制序列
反码:除了符号位以外全部取反(1变0,0变1)
补码:反码+1
正数的原码反码补码相同,无需变换,负数按照如上规则进行变换
因为一个整型存储需要4个字节,所以可以展开为32位bit位,下面举几个例子辅助理解
在计算机中存储的实际上是补码,但是打印数字最终还是会以原码对应十进制数字返回

当一些短整型和字符类型进行计算的时候会有一个特殊操作,叫做整型提升,会将其bit位提升到32位来进行计算
整型提升的过程中需要补位,对于有符号的整数来说补最高位,无符号整数补0
下面用几个例子加深理解
举例1
我们知道strlen函数返回的是一个无符号整型size_t类型的数据,所以无符号整型相加减的结果也会被判定为无符号整型,注意下图是在32位环境下运行的,64位运行结果不同但是依旧为正

举例2
#define _CRT_SECURE_NO_WARNINGS 1
#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);
//11111111 11111111 11111111 11111111
//截取8位 a存储11111111
//b是有符号整型与a同理
//b存储11111111
//c是无符号整型
//11111111 11111111 11111111 11111111
//c截取8位
//c存储11111111,但是记住c是无符号
//当输出时,先整型提升
//a,补1
//11111111 11111111 11111111 11111111 补码
//10000000 00000000 00000000 00000001 原码
//按照输出%d有符号整型来看,应该输出-1
//b同理输出-1
//c,补0
//00000000 00000000 00000000 11111111 补=原
//按照有符号整型%d来看,应该输出255
return 0;
}

举例3
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
char a = -128;
printf("%u\\n", a);
//10000000 00000000 00000000 10000000 原
//11111111 11111111 11111111 10000000 补
//截取8位是10000000
//输出时,先整型提升,补1
//11111111 11111111 11111111 10000000
//按照无符号整型的输出
//应输出4,294,967,168(32位环境)
return 0;
}

举例4
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
char a = 128;
printf("%u\\n", a);
//00000000 00000000 00000000 10000000 原=补
//a存储10000000
//输出时整型提升,补1
//11111111 11111111 11111111 10000000
//按照无符号整型输出4,294,967,168
return 0;
}
、
举例5
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 – i;
}
printf("%d", strlen(a));
//因为char类型的范围是-128-127
//我们计算一下边缘值
//当char x=127
//00000000 00000000 00000000 01111111
//x存储01111111
//x+1时,先进行整型提升,补0
//00000000 00000000 00000000 01111111
//00000000 00000000 00000000 00000001 相加
//00000000 00000000 00000000 10000000
//截取8位
//x存储10000000
//回想举例3中字符类型的a存储10000000,是-128
//所以我们可以得出char类型突破边界后会循环
//所以当a[i]等价与'\\0'的ACSII值0时,即可停止循环
//因为127+1得到-128,所以-128-1可以得到127
//-1 -2 -3 …-128 127 126 … 3 2 1 0
//128+127=255,共有255个数字,所以长度为255
return 0;
}

举例6
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\\n");
}
//因为unsigned char类型范围是0-255,所以我们计算一下边界
//当i=255时,进行整型提升
//00000000 00000000 00000000 11111111
//00000000 00000000 00000000 00000001 加一
//00000000 00000000 00000001 00000000
//i存储00000000,是0
//说明一直在0-255循环,会死循环打印
return 0;
}
举例7
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i–)
{
printf("%u\\n", i);
}
//同样计算边界
//i=0时,进行整型提升
//00000000 00000000 00000000 00000000
//减一得到
//11111111 11111111 11111111 11111111
//i存储11111111
//输出时进行整型提升
//11111111 11111111 11111111 11111111
//输出结果为4,294,967,295
//所以依然是死循环
return 0;
}

2.浮点数在内存中的存储
根据IEEE 754规定
对于32位浮点数(float),1位符号位S,8位存储指数E,剩余23位存储有效数字M
对于64位浮点数(double),1位符号位,11位指数位,剩余52位存储有效数字M
所以一个浮点数可表示为(-1)^S *M*2^E,其中M大于等于1且小于2,S=0为正,S=1为负
因为有些小数很难用二进制精确表示,可能超出了有效数字位数之后依然没能完全表示,所以浮点数的存储会有一些误差
对于M,既然1<=M<2,所以M的结果应该是1.xxxxxxx,因为默认第一位是1,所以存储有效数字的时候,可以省略这个1,从而可以多储存一位数字,整数部分的1等到读取的时候再加回去就行
对于E,E是一个无符号整型,如果用8位二进制存储,取值范围是0-255,11位二进制存储,取值为0-2047,但是科学计数法中E可以存在负数,所以需要加上一个中间值来保证存储进去的E一定是非负数,从而符合E无符号整型的类型,对于8位E,加上127,对于11位E,加上1023
E的存储分为三种情况
以8位E为例,如果E存储了00000000,IEEE规定此时的E=1-127,此时有效数字部分不再加上第一位的1,还原为0.xxxxxx的小数,表示一个很接近0的数字
如果E存储11111111,则表示无穷大,正负取决于S符号位
当E中不全是0或1时,是常规情况,按照存储规则还原出浮点数即可
下面举个例子辅助理解
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;
printf("n的值为:%d\\n", n);
printf("*p的值为:%f\\n", *p);
//n存储int类型的9
//00000000 0000000 00000000 00001001 (1)
//所以n中实际存储的就是(1)这个二进制序列
//输出时,按照对于规则输出
//对于%d,按照有符号整型输出,结果是9
//对于%f,按照浮点数的规则拆解二进制序列
//0 00000000 000000000000000000001001
//S E M
//E为全0,表示接近0的浮点数,所以输出0.000000
*p = 9.0;
printf("n的值为:%d\\n", n);
printf("*p的值为:%f\\n", *p);
//n中存储9.0,按照浮点数规则存储
//9.0=1001.0,则将小数点左移3位,E=0+3+123=130
//M=1.0010,去掉首位1,存储0010+19位0
//0 10000010 00100000000000000000000 (2)
//所以n中存储(2)序列
//按照有符号整型输出时,则输出1,091,567,616
//按照浮点数输出时,则输出9.000000
return 0;
}

3.大小端模式
内存中存储数据有两种方式
大端:高字节数据存储到低地址
小端:低字节数据存储到低地址
44是a的低字节数据,存储在低地址(同一行地址从左向右升高)
那么可以得出,当前运行环境是小端模式
如果是大端,那么内存中应该是11 22 33 44

我们也可以写一个程序,直接判断当前环境是大端还是小端
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int test() {
int a = 1;
char* p = (char*)&a;
if (*p == 1)return 1;
else return 0;
//如果小端,那么a内存中应该存储
//01 00 00 00
//因为char*类型操作权限是一个字节
//刚好取出第一个字节的01
//所以如果*p==1,是小端
//*p==0,是大端
}
int main()
{
if (test())printf("小端\\n");
else printf("大端\\n");
return 0;
}
补充一个练习加深印象
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//注意在x86环境运行,x64会报错
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);//%x,按照16进制输出
//对&a是整个数组的地址,类型为int(*)[4]
//1 2 3 4 x x x
// ptr1
//所以ptr目前指向4后方一个元素
//ptr[-1]等价于*(ptr-1),所以按照16进制输出4的结果是4
//对于(int)a,是将a的首元素地址转换成一个整型类型的整数
//那么(int)a+1就是移动了一个字节,而不是四个字节
//因为数组中的数据连续存储,将它们都转换成内存中存储形式观察一下
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
// ptr2
//ptr2指向该位置,解引用时取出四个字节
//那么*ptr2=0x02000000
//输出结果是2000000
return 0;
}




