【C语言】数据在内存中的存储(原码反码补码、大小端存储、浮点型在内存中的存储)

文章目录

    • 一、了解数据类型的意义
    • 二、整数在内存中的存储
      • 1、原码、反码和补码
      • 2、小端存储和大端存储
      • 3、整型数据的范围
    • 三、浮点型在内存中的存储
      • 1、一个例子
      • 2、浮点数存储规则
    • 四、总结

一、了解数据类型的意义

例如一个int整型,我们知道它的大小为4个字节,一个字节为8个bit位。
如一个int a=10在内存中通过16进制和2进制分别表示为

//16进制表示
0x0000000a
//2进制
00000000 00000000 00000000 00001010

而如果是一个char a=10的话,在内存中用16进制和2进制分别表示为

//16进制表示
0x0a
//2进制表示
00001010

由此可知:

  1. 数据的类型决定了数据在内存中存储的是怎样的空间
  2. 数据的类型决定了能开辟多少的空间大小

常见的数据类型有:

整数类型

char

unsigned char  signed char

short
unsigned short [int]   signed short [int]

int
unsigned int  signed int

long
unsigned long [int]  signed long[int]


浮点类型

float

double

而数据在内存具体是怎么存储的,接下来将探讨这个问题。

二、整数在内存中的存储

1、原码、反码和补码

在整数中的三种表示方法:

  • 原码:直接将一个数写成2进制就是原码
  • 反码:原码的各个bit位取反就是反码
  • 补码:反码+1就是反码(补码也是内存中存的)

并且这些表示方法都拥有两个部分,符号位数值位两部分,符号位用0表示正数,用1表示负数,数值位在负数的时候三种表示方法各不相同。
正数原码、反码和补码相同
如:
int a=10
int b=-10

00000000 00000000 00000000 00001010 //a=10 原码 反码 补码相同

10000000 00000000 00000000 00001010 //b=-10 原码
11111111 11111111 11111111 11110101 //b=-10 反码
11111111 11111111 11111111 11110110 //b=-10 补码


为什么整数在计算机中存的是补码?

因为在计算机中用补码存储可以将符号位和数值位统一处理。
也可以统一进行加减运算(CPU只能进行加法操作),并且原码与补码的转换方式是相同的,不需要额外的硬件电路

如果用原码进行1+(-1)的操作

00000000 00000000 00000000 00000001  //1原码
10000000 00000000 00000000 00000001  //-1原码
10000000 00000000 00000000 00000010  //原码相加-2 得不到0

如果用补码进行1+(-1)的操作

  00000000 00000000 00000000 00000001  //1补码
  10000000 00000000 00000000 00000001  //-1原码
  11111111 11111111 11111111 11111110  //-1反码
  11111111 11111111 11111111 11111111  //-1补码
1[00000000 00000000 00000000 00000000]  //1补码和-1补码相加 得33位 但int只有32位所以为0

原码与补码的转换方式是相同的,都可以通过取反+1来得到对方
下面用Int a=-10 来举例

10000000 00000000 00000000 00001010 //-10原码
11111111 11111111 11111111 11110101 //-10反码
11111111 11111111 11111111 11110110 //-10补码
10000000 00000000 00000000 00001001 //补码取反
10000000 00000000 00000000 00001010 //+1得到原码


2、小端存储和大端存储

下面来看这样一个现象

int main()
{
	int a = 10;
	//00000000 00000000 00000000 00001010 2进制表示
	//00 00 00 0a 16进制表示
	return 0;
}

【C语言】数据在内存中的存储(原码反码补码、大小端存储、浮点型在内存中的存储)_第1张图片
当我们在内存中搜索a的地址的时候发现,显示的0a 00 00 00 顺序并不和我们所表达的一样,
这是为什么呢?

  • 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址
    中;
  • 小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地
    址中。

上图中0a在低地址,数据低位保存在内存中的低地址中,所以是小端。

下面通过一个程序判断自己机器是大端还是小端:
a=10中存储为00 00 00 0a
如果是大端高地址存储低位 char类型截断低位得到00
如果是小端低地址存储低位 char类型截断低位得到0a

void Check(int a)
{
	char* p = (char*)&a;
	if (*p)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
}

int main()
{
	int a = 10;
	Check(a);

	return 0;
}

3、整型数据的范围

让我们用char来讨论一下
字符类型有三种,分别是:char(有编译器是signed char,有编译器是unsigned char,vs中是signed char ) 、signed char 、unsigned char。

  • unsigned char 代表无符号字符类型,没有负数
  • signed char 代表有符号字符类型,有负有正

signed char类型通过二进制码可以得知
范围是00000000~11111111,下面来看看具体十进制范围是多少

//补码形式
00000000 //0
00000001 //1
00000010 //2
...
01111111 //127(128-1)
-----------------
10000000 //-128   11111111 10000000 //符号位始终不变 进位被舍弃
10000001 //-127   11111110 11111111
10000010 //-126
...
11111111 //-1 	  10000000 10000001

所以signed char 的范围是-128~127
而unsigned char因为不用考虑符号位就是0~255(128+127)

下面来看一个例题,在vs环境下,所以char默认是signed char类型

int main()
{
    char a[1000];
    int i;
    for(i=0; i<1000; i++)
   {
        a[i] = -1-i;
   }
    printf("%d",strlen(a));
    return 0;
}

输出的结果是:255
我们已知signed char最低范围是-128,所以当a[127]=-128的时候,在进行-1,就会变为127。
所以直到a[255]=0,strlen(a)取断

//补码运算
  10000000  -128补码
  11111111  -1补码
1[01111111] -128+(-1)=127
  127+(-1)
  01111111
  11111111
1[01111110] 126
...
  00000000 

并且由此可知,signed char 范围(-128~127)如果一直-1,似乎会进入一个循环,-128+(-1)=127,127+1=-128


三、浮点型在内存中的存储

浮点型主要是float和double,它们也是有自己的范围。
float在32位系统下是4个字节,在64位系统下是8个字节

1、一个例子

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;
}

它的运行结果如下
【C语言】数据在内存中的存储(原码反码补码、大小端存储、浮点型在内存中的存储)_第2张图片
这是为什么呢?
明白它之前,让我们先来看看浮点数在内存中是如何存储的。

2、浮点数存储规则

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
V=(-1)^S * M * 2^E
(-1) ^ s 表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。

例如0.5 二进制表示为1.0 * 2^(-1)
(-1)^0  1.01  2^2

float的空间分配
【C语言】数据在内存中的存储(原码反码补码、大小端存储、浮点型在内存中的存储)_第3张图片

在存入:
国际标准IEEE 754对E和M还有一些特别的规定:
在M中
因为1<=M<2,所以M可以写成1.xxxxxx,xxxxxx是小数的形式,因为在保存M=1.xxxxx的时候,第一位默认总是1,所以可以被省去,只保留后面小数部分,这样就可以多存一位。

在E中(首先E是一个无符号整数 unsigned int)
如果E只有8位,就代表范围是0~255,64位就是0 ~ 2047,但是E是可以出现负数的,所以所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。


如存入一个9.0
先化为二进制9.0->1001->1.001*2^3
E中3+127为

00000011 //3
01111111 //127
10000010 //130

M中 001存入为

00100000000000000000000

则存入就为

0 10000010 00100000000000000000000 //总共32位



在取出:
E在取出时
1.如果E不全为1或者不全为0,那么在取出时要减去中间值-127,再将M加上1。
2.如果E全为0,这时E的指数位为1-127,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
3.如果E全为1,这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

让我们回头看
【C语言】数据在内存中的存储(原码反码补码、大小端存储、浮点型在内存中的存储)_第4张图片

//整数9在内存中存储
00000000 00000000 00000000 00001001
//那么如果通过浮点型读取
//V=(-1)^0*00000000000000000001001*2^(1-127)趋近于0 所以结果为0
0 00000000 00000000000000000001001  
-------------------------------------
//将浮点型9.0存入 9.0->1001->(-1)^0 3 1.001
0 10000010 00100000000000000000000
//通过整型读取
01000001000100000000000000000000 //结果就是一个很大的数
//取出 就是9.0


四、总结

了解数据在内存中的存储,对于出现一些不合常理的值时,能通过研究其内存布局从而判断其出现的原因。

你可能感兴趣的:(C语言基础,c语言,开发语言)