深度剖析数据在内存中的存储
类型的意义:
1.使用这个类型开辟的内存空间的大小(大小决定了使用范围)
2.如何看待内存空间的视角
类型的基本归类:
整型家族:
char //字符的本质是ASCII码值,是整形,所以划分到整形家族
unsigned char
signed char //char a; char到底是unsigned char还是signed char,取决于编译器,标准未定义
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int --> int
long
unsigned long [int]
signed long [int]
long long
unsigned long long [int]
signed long long [int]
注意:除了char类型的定义是由编译器决定是否默认有符号位的,其他整型类型的数据,默认为signed类型
在日常生活中,有些数据是没有负数的,比如:身高、体重、年龄,这种情况就要使用unsigned 类型的数据。
而在unsigned类型数据中,二进制的最高位变成了有效数据位。
而在signed类型数据中,二进制的最高位变成了符号位。
浮点型家族(只要是表示小数就可以使用浮点型数据):
float //精度较低,存储的范围较小
double //精度较高,存储的范围较大
构造类型(自定义类型):
我们可以自己创建出新的类型:
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
指针类型:
int* p;
float* p;
char* p;
void* p;
空类型:
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型
整型在内存中的存储:
一个变量的创建是要在内存中开辟空间。空间的大小是根据不同的类型而决定的。
比如:
//数值的表示形式:
//2进制
//8进制
//10进制
//16进制
//整数的2进制表示也有三种表示形式:
//正数的原、反、补码相同
//负数的原、反、补码需要进行计算得出
//1.原码:直接通过正负的形式写出的二进制序列就是原码
//2.反码:原码的符号位不变,其他位按位取反得到的就是反码
//3.补码:反码+1就是补码
//为了方便表示,通常把4个二进制位变成一个16进制位,但是内存中还是以二进制补码的形式储存的
int a=10; //正数在内存中如何存储,一个整型占4个字节
//a因为是正数,所以原反补码都相同
//bit:00000000000000000000000000001010 -原
//hex:0x00 00 00 0a //内存展示给我们的数据
//bit:00000000000000000000000000001010 -反
//hex:0x00 00 00 0a
//bit:00000000000000000000000000001010 -补
//hex:0x00 00 00 0a
int b=-10; //负数在内存中如何存储
//bit:10000000000000000000000000001010 -原
//hex:0x80 00 00 0a
//bit:11111111111111111111111111110101 -反 符号位不变
//hex:0xff ff ff fa
//bit:11111111111111111111111111110110 -补 反码+1
//hex:0xff ff ff f6
//补码的补码等于原码
//-10的原码:10000000000000000000000000001010
//-10的反码:11111111111111111111111111110101
//-10的补码:11111111111111111111111111110110
//-10的补码的原码:11111111111111111111111111110110
//-10的补码的反码:10000000000000000000000000001001
//-10的补码的补码:10000000000000000000000000001010
对于整型来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理。
同时,加法和减法也可以统一处理(CPU只有加法器),此外,补码和原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
我们可以看到在内存中对于a和b分别存储的是补码,但是我们发现顺序有点不对劲。
大小端介绍
大小端:只是数据在内存中存储的方式,取决于硬件电路。
大端【字节序】存储:
把一个高位字节序的内容存放到低地址处,把低位字节序的内容存放到高地址处,这就是大端字节序存储。
小端【字节序】存储:
把一个低位字节序的内容存放到低地址处,把高位字节序的内容存放到高地址处,这就是小端字节序存储。
口诀:左低右高,正序大端,逆序小端。
****百度系统工程师面试题,要求你写一个简单的程序,判断数据是大端存储还是小端储存****
整型存储练习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;
}
-1的原码:100000000000000000000000000000001
反码:111111111111111111111111111111110 (原码除了符号位其他位取反)
补码:111111111111111111111111111111111 (反码+1等于补码)
但是由于a是字符型的数据,只有一个字节(8个bit位),但是-1的补码有32个bit位。
所以需要把补码的数据:111111111111111111111111111111111,截断成:11111111,存放进a里,这种操纵称为截断数据。
截断位时,截断的是数值对应的二进制序列的低位,跟大小无关
整形提升的时候有符号位看高位是0/1进行补位,无符号位高位补0。
先用unsigned/signed决定整型提升补位的是0还是1,再用数据表示类型符%d决定是否有符号用于决定补码转原码的流程。
整型存储练习2:
int main()
{
unsigned int i;
for(i=9;i>=0;i--)
{
printf("%u\n",i);
}
return 0;
}
问:思考这个程序的效果是怎么样的?
结果:死循环。
解析:i为一个unsigned int类型,for循环里循环条件为i小于等于0,通常我们会以为这个程序只会打印9--1的数字。但是,由于我们的i是一个unsigned int类型的,是一个无符号数,当程序执行到-1时,-1存到i中,i就把-1看成一个很大的数。
-1的原码:10000000000000000000000000000001
-1的反码:11111111111111111111111111111110
-1的补码:11111111111111111111111111111111 (最高位为符号位)
因为我们的i是无符号(unsigned int)类型的数据,当-1存到i的时候,i会把最高位当成有效位。
所以-1存到i的时候,是一个非常大的整数。
整型存储练习3:
int main()
{
char a[1000];
int i;
for(i=0;i<1000;i++)
{
a[i]=-1-i;
}
printf("%d",strlen(a));
return 0;
}
问:a执行完的长度是多少?
结果:255。
char 类型的取值范围:~127-128。
当程序执行到-128的时候,在进行-1-128的操作由于char的取值范围是~128-127,当i变成-129的时候,i会变成127
-129的原码:10000000000000000000000010000001
-129的反码:11111111111111111111111101111110
-129的补码:11111111111111111111111101111111
但是a是char类型的数据,只能存放一个字节,8个bit位,所以要进行截断:01111111。
截断的数据等于a里面存储的数据:01111111。
但需要进行操作时,根据是否是unsigned / signed 的类型进行提升。
如果是unsigned ,就不需要根据最高位的符号位进行提升。
如果是signed,就需要根据最高位的符号位进行提升。
char在visual studio 2022编译器中默认是signed 类型的,所以需要根据符号位进行整型提升。
提升后的值:00000000000000000000000001111111
根据符号位,判断它是一个正数,正数原反补码相同,所以提升后的值等于127。像一个圆环一样循环下去。
整型数据存储练习4:
unsigned char i=0;
int main()
{
for(i=0;i<=255;i++)
{
printf("hello world!\n");
}
return 0;
}
问:会打印多少个hello world!
结果:死循环。
unsigned char 类型取值范围:0~255。
但是程序for循环到256,超越了i的取值范围,它会变成0,重新执行循环。
整型数据存储练习5:
int main()
{
if(strlen("abc")-strlen("abcdef")>=0)
printf("<\n);
else
printf(">\n);
return 0;
}
问:打印>还是<?
结果:<.
strlen函数一个unsigend 类型的数据,返回的是无符号的数据,两个无符号的数据相减
正常的计算3-6的值等于-3
但是strlen是无符号的,两个无符号数进行操作得到的也是一个无符号的数
-3就转换成一个非常大的正数