C语言学习 -- 整型与浮点型在内存中的存储

Posted 庸人冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言学习 -- 整型与浮点型在内存中的存储相关的知识,希望对你有一定的参考价值。

数据类型介绍

C语言类型分为两类:

  1. 内置类型:C语言本身就有的类型。
  2. 自定义类型(构造类型)。

内置数据类型

内置数据类型又可以划分为整型和浮点型,char类型底层存储的ASCII码值,因此也被归类在整型中。

关键字类型内存空间
char字符数据类型1B
short短整型2B
int整形4B
long长整型4B
long long更长的整形8B
float单精度浮点数4B
double双精度浮点数8B

C99之后,C语言的内置数据类型新增了布尔型、复数型、虚数型,这里不做讨论。

整形

整型的取值范围定义在头文件**limits.h**中。

DatatypeSizeUnsignedSigned
char1Byte0~28-1-27~-27-1
short[int]2Byte0~216-1-215~215-1
int4Byte0~232-1-231~231-1
long[int]4Byte0~232-1-231~231-1
long long[int]8Byte0~264-1-263~263-1

注意点:

  • char默认类型取决于编译器大多数编译器下char默认都是signed
  • 其他整形类型默认都是**signed**。

浮点型

浮点型的取值范围定义在头文件**float.h**中

DatatypeSize指数位尾数位取值范围有效数字
float4Byte823-21282128 ≈ -3.4E38 ~ +3.4E387 或 8位
double8Byte1152-2102421024 ≈ -1.797E308 ~ +1.797E30815 或 16位

自定义类型(构造类型)

  1. 数组类型:去掉数组名剩下的就是数组的类型,如: int [10] ,可以用sizeof(int [10]) 计算数组大小;

  1. 结构体类型 :struct
  2. 枚举类型::enum
  3. 联合类型:union

指针类型

指针类型的占用空间在相同平台下大小相同,作用是存放地址。

例如:int *pi char *pc float *pf

空类型

void表示空类型(无类型)

通常应用于函数的返回类型、函数的参数、指针类型。

void test(void)  // 无返回值, 无参数
{
    // ... 
}
void* test() // 返回void* 类型指针,无参数()中不写默认void
{
	//...
}
(void) // 表示明确指定该函数无参,传参则会报警告
()     // 也代表无参,但是如果传参给函数不会报警告

类型的意义

  1. 类型决定开辟内存空间的大小,大小决定了使用使用范围。
  2. 如何看待内存空间的视角。
int main()
{
	int a = 10;     // 向内存申请4个字节
	float f = 10.0; // 向内存申请4个字节
	return 0;
}

例如int变量afloat变量f 存入的都是10,但是在内存中表现形式完全不同。

整形在内存中的存储

定义两个int类型变量a,b ; a = 10, b = -10观察它们在内存中的存储情况。

int main()
{
	int a = 10;
	int b = -10;
	return 0;
}


通过输出的结果,我们可以看到 变量a中存放的值为 0a 00 00 00 , 而变量b中存放的值为 f6 ff ff ff。
这两串数字和我生活中所使用的10和-10完全不同,那它们代表的含义是什么呢?
首先,我们知道数据在内存中都是以二进制的形式存储的,但是为了方便观察,在VS的内存窗口中,我们采用的是16进制的格式显示数据,所以上面两串数字是 10 和 -10 在内存中存储的十六进制表现形式。
不过,就算知道这两串数字是16进制表示的也还是很难与 10 和 -10 联系在一起,此时我们就需要了解,原反补和大小端的概念。

原码、反码、补码概念

计算机中的整形有三种表示:原码补码补码

内存中存储的是二进制的补码

无符号数

无符号数没有负数,它的原、反、补码相同,即该无符号数(十进制)对应的二进制表示形式。

有符号数

有符号数中,使用二进制位得最高位作为符号位,剩余的二进制位是数值位

符号位:表示一个有符号数的正负,0表示**正 +1表示负 - **。

数值位:表示一个有符号数的绝对值

正数

正数的原、反、补码相同。

符号位为0,其余位为该正数的二进制表示形式。

例如:

7 == 00000111

7 == 00000111

7 == 00000111

负数

原码:符号位为1 ,数值位为该有符号数绝对值的二进制表示。

反码:原码的基础上,符号位不变,剩余位按位取反

补码:反码的基础上 + 1

补码 --> 原码的过程相反。

例如:

-7 == 10000111

-7 == 11111000

-7 == 11111001

为什么存储的是补码?

计算机在存储整形数据时中存储的是二进制的补码,使用补码的方式存储是因为可以用加法同时处理加法与减法运算(CPU只设计了加法器)。

例如: 1 - 1 = 1 + (-1) = 0

  1. 用原码方式相加

    1 = 00000001,-1 = 10000001

    00000001 + 10000001 = 10000002 = -2 (错误)

  2. 用反码方式相加

    1 = 00000001,-1 = 11111110

    00000001 + 11111110 = 11111111 = 10000000 = -0

    -0 虽然和 0 相同,但是用2个组合来表示一个数有些浪费,因此不可取。

  3. 用补码方式相加

    1 = 00000001,-1 = 11111111

    00000001 + 11111111 = 100000000 = 0 (正确)

    经过计算,1会进到第8位,多出的最高位溢出被舍弃,0则变为符号位,因此得出的结果为正数,他的原反补相同。

大小端概念

内存中存储数据是以字节为单位存储的,分为大端字节序小端字节序

大端(字节序)存储模式:是指数据的低位字节保存在内存的高地址中,而数据的高位字节,保存在内存的低地址中;

小端(字节序)存储模式:是指数据的低位字节保存在内存的低地址中,而数据的高位字节,保存在内存的高地址中。

举例

int main()
{	
    int b = 0x11223344;
    return 0;
}
// 小端字节序


通过上面的一些解释,我们大致了解了大小端的存储模式,那么如何判断当前硬件平台是的存储模式呢?

我们可以有如下的思路:

  1. 大端字节序,是数据的高位字节存储在内存的低地址中,低位字节存储在内存的高地址中。

  2. 小端字节序,是数据的高位字节存储在内存的高地址中,低位字节存储在内存的低字节中。

  3. 假设创建一个short类型的变量a,并给其赋值为1,它的十六进制表示为0x00高位字节, 0x01低位字节

  4. 此时使用一个char* 类型的指针变量pa接收变量a低地址,并访问这个地址的内容。

  5. 如果*pa == 1 则说明是小端字节序。

  6. 如果 *pa != 1 则说明是大端字节序。

代码:


int LitOrBig(void)
{
    short a = 1;
    // char* pa = (char*)&a;
	return *(char*)&a; // 返回1是小端,返回0是大端
}
 
int main()
{
	
    int ret = LitOrBig();
    if(ret) 
    {
        printf("小端字节序");
    } else {
        printf("大端字节序");
    }
    return 0;
}

二进制10000000的意义

按照之前的原反补规则,二进制补码10000000表示的应该是-0,那么-00就重复表达了一个数字。因此在处理10000000的二进制补码时做了特殊处理,即不进行原反补的转换,直接使它表示为-128。如图:

因此,只要是有符号数,不论它是什么整型,如果符号位为1(即负数),其余位均为0,则这个二进制数表示为**-(1 × 2n-1)**,n为二进制位数,n-1是该符号位的位权。

浮点型在内存中的存储

浮点数家族包括:float、double、long double类型,浮点数表示的范围在**float.h**中定义。

3.14159E10,表示 ``3.14159×1010

例如:

int main()
{
    double d = 1E10;
    printf("%lf\\n",d);   // 10000000000.000000
    return 0;
}

二进制浮点数的表示形式

根据国际标准IEEE 754对任意一个二进制浮点数V可以表示成下面的形式:

(-1)S* M *2E

(-1)s:表示符号位,当S = 0, V为正数,当S = 1, V为负数

M:表示有效数字,大于等于1,小于2.

2E:表示小数点移动位数

十进制浮点数与二进制浮点数转换的规则

  1. 小数点左边即整数部分,规则和整形相同,逢2进1,小数点前第1位的位权是20,第二位的位权是21,以此类推…

    例如:二进制整数 1101 = 1 * 23 + 1 ^ 22 + 0 * 21 + 1 * 20 == 8 + 4 + 0 + 1 = 13 十进制整数

  2. 小数点右边即小数部分,规则是,逢2进1,小数点后第一位为位权是2-1,第二位的位权是2-2,以此类推…

    例如:二进制小数 0.1101 = 1 * 2-1 + 1 * 2-2 + 0 * 2-3 + 1 * 2-4 = 0.5 + 0.25 + 0 + 0.0625 = 0.8125 十进制小数

通过上面的规则,换算9.0:

9.0对应的二进制 = 1001.0,而1001.0 = 1.001 * 23,9.0是正数,所以S = 0。

因此可以按照IEEE754的规则写成: (-1)0 * 1.001 * 23 ,其中S = 0,M = 1.001,E = 3

存储方式

在该规定的表示形式中,(-1) 与 底数2 是常量不会变化,因此在浮点数在内存中只会存储S,M,E 三个值。

IEEE 754规定:对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位位有效数字M

对于64位的浮点数,最高的1位是符号位s,接着的11位是指数E,剩下的52位为有效数字M

IEEE 754对有效数字M和指数E,还有一些特别的规定:

有效数字M的规定:

  1. 因为M总是可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分,并且1≤M<2。所以IEEE 754规定,因为M第一位总是1,所以可以不存储在内存中,只保留小数部分,小数部分不够23位时则在后面补0

    例如: 1.01在内存中只存储了01,小数点前的1被舍去。并在后面补21个0:01000000000000000000000

  2. 读取时,再把第一位的1加上去,这样做的目的可以节省1位有效数字来存储更多的数据。

    例如:以32位浮点数为例,如果存储M的第一位数字1,则剩余小数部分只有22位可以使用,但是将M的第一位的1舍去以后,就可以将23位都用来保存小数部分。

指数E的规定

保存指数E的情况比较复杂:

首先,E为一个无符号整数(unsigned int) 这意味着,如果E8位,它的取值范围是0~255;如果E11位,它的取值范围是0~2047。但是,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实取值必须再加上一个中间数。对于8位的E,这个中间数是127;对于11位的E,这个中间数时1023。比如,210E10,所以保存在32位浮点数时,实际保存的是10 + 127=137,即10001001

举例:

int mian()
{
    float f = 5.5;
    return 0;
}

5.510进制 = 101.12进制 ,其中 0.12进制 = 2-1 = 0.510进制

(-1)0 * 1.011 * 2^2 ,S = 0,M =1.011, E = 2

E + 127 = 129 = 10000001 , M去掉1 = .011

得到: 0 10000001 01100000000000000000000 = 5.5

将得到的数转换为16进制 与内存窗口中显示存储的值做对比:

01000000 10110000 00000000 00000000

0x 40 B0 00 00 (小端字节序,低字节序在低位,高字节序在高位)

然后,指数E从内存中取出还可以再分成三种情况

E不全为0或不全为1

按照存储的规则反向计算,对于32位或64位的浮点数,E - 127或1023,就可以得到真实指数,

例如:0 01111110 00000000000000000000000

  1. 其中E = 01111110,代表真实指数加127之后的结果,因此先将该二进制数换算位十进制得到:126

    再让 126 - 127 = -1,就得到了真实指数 = -1。

  2. 有效数字在从内存中取出是需要在第一位加1,得到 10000000000000000000000,转换为十进制就是1.0

    这样就得到了 M = 1.0。

  3. 在二进制序列中,第一位就代表S,所以S= 0

  4. 将得到3个数还原为 (-1)^S^ * M * 2E 的形式 == (-1) 0 * 1.0 * 2 -1 == 0.5

这样就计算出该二进制序列对应的十进制数了。

E全为0

E为全0,表示真实指数 + 127 = 0,这说明真实指数是-127,2-127是一个非常小的数字,几乎接近于0,因此IEEE 754规定:

这时,浮点数的真实指数 == 1 - 127(或1-1023), 并且有效数字 M 不再加上第一位的1,而是还原为0.xxxxxx的小数

E全为1

E为全1时,表示真实指数+127 = 255,这说明整指数是+127,2127是一个非常大的数字,趋于无穷。

这时,如果有效数字M全为0,表示± 无穷大(正负取决于符号位s);

以上是关于C语言学习 -- 整型与浮点型在内存中的存储的主要内容,如果未能解决你的问题,请参考以下文章

C语言学习笔记(10)数据在内存中的存储

剖析C语言数据在内存中的存储

C语言进阶笔记用图文+代码让你深度理解数据在内存中的存储

[C语言进阶]数据的存储

C语言从青铜到王者第五篇·数据在内存中的存储

C语言从青铜到王者第五篇·数据在内存中的存储