小编认为要想成为一个好的程序员,不能仅仅只做到会使用,而要做到理解其本质 。做到可持续发展。接下来小编会向大家介绍数据在内存中究竟是如何存储与运算的,也算是修炼内功了吧。
目录
char:unsigned char、signed char
short:unsigned short、signed short
int:unsigned int、signed int
long:unsigned long、signed long
值得注意的是,char类型也属于整型家族原因是:字符本质为ASCII值,是整型,所以划分到整型家族。另外char究竟是singed char还是unsigned char取决于编译器的底层实现。
float、double、long double
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
int * 、char *、float*、void* ……
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型等
计算机中的整数有三种2进制表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”。
而数值位正数的原、反、补码都相同。负整数的三种表示方法各不相同。
原码:直接将整型数字按照正负数形式翻译成二进制,即可得到原码
反码:将原码的符号位不变,其他位依次按位取反即可得到反码
补码:将反码加一即可得到补码
在计算机系统中,整型数值一律使用补码来表示与存储,但是为什么呢?
1.使用补码可以将符号位和数字域统一处理。
2.加法和减法可以统一处理(CPU只有加法器)。
例:CPU计算1 - 1时,会计算1 + (-1)
3.补码与原码相互转换,其运算过程相同,不需额外的硬件电路。
接下来,看看下面这张图:
变量a的补码用十六进制表示不是0x00000001吗,在内存中看怎么会是这个样子呢?这就不得不提到大、小端字节序存储模式了。
大端(存储)模式:
是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式:
是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
例:一个 16bit的short型变量x ,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节, 0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即 0x0011中。小端模式,刚好相反。
从上图可以看出小编所用的环境是小端字节序存储模式,那么为什么会有大小端之分呢?
这是因为在计算机系统中是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了 8bit 的char之外,还有 16bit 的short型,32bit 的long型(要看具体的编译器)。另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
我们常用的X86(32位)结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
百度就曾有一年的笔试题,考过这个知识点。这道题,我将讲述两种解法:
- #include
- int check_sys_1()
- {
- int i = 1;
- char* p = (char*)&i;
- return *p;
- }
- //简化后:
- 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;
- }
- #include
- 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;
- }
联合体的所有成员在内存中具有相同的首地址,共占同一段内存空间,这些成员可以相互覆盖。
利用该条特性实现大端与小端的判断,该方法实在"秀"。
C的整型算术运算总是至少以缺省整型(即默认整型)类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU (general-purpose CPU) 是难以直接实现两个字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
整型提升是按照变量的数据类型的符号位来提升的,若无符号直接补0。
以char类型为例:
字符型数据char可分为unsigned char(无符号)和signed char(有符号)
具体char代表的是有符号字符型还是不符号字符型,取决于你编译器的具体实现。以VS2019为例,char所代表的便是signed char。
由此可见,signed char的取值范围是-128到127,unsigned char的范围是255.
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数可以表示成下面的形式:
(-1)^S * M * 2^E(即用科学计数法表示)
(1)对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
(2)对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
计算机内部保存M时,默认这个数的第一位总是1。因此,可以被舍去,只保存后面的小数部分,读取时再将第一位的1加上。节省一位有效数字,可提高小数部分的精确度。
(1)放入
E为一个无符号整数。若E为8位,它的取值范围位0-255。若E为11位,它的取值范围为0-2047。
但科学计数法中的E是可以出现负数的。所以IEEE 754规定,存入内存时E的真实值必须加上一个中间值。对于8位的E,中间值位127。对于11位的E,中间值为1023。
(2)取出
1.E不全为0或不全为1
E减127(1023)得E的真实值——即正常反向计算
2.E全为0
浮点数的指数E等于1-127(或者1-1023)即为真实值。有效数字M不再加上第一位的1,而是还原为0.xxx的小数。这样是为了表示+/-0,以及接近于0的很小的数字。