数据在内存中是如何存储的?(上)

一. 整型数据的二进制表示

整型数据的二进制表示形式有三种:原码,反码,补码
1.原码:根据类型来表示二进制位数,最高一位为符号位(1表示负数,0表示正数)
2.反码:原码的符号位不变,其余按位取反;
3.补码:反码 + 1

注意一点:正数和负数的原码,反码,补码有区别!!!
正数:原码,反码,补码是相同的。
负数:原码,反码,补码按上面条件改变

上面三种是怎样表示呢?举一个简单例子

`创建一个整型变量num,在内存中开辟了四个字节的空间存放数据,四个字节 = 32 个比特位,也就是32位二进制

int num = 10;

原码:
00000000000000000000000000001010
反码:
00000000000000000000000000001010
补码:
00000000000000000000000000001010

int num = -10;
原码:
10000000000000000000000000001010
反码:(符号位不变,其余按位取反)
1111111111111111111111111111111110101
补码:(反码 + 1)
1111111111111111111111111111111110110

注意三点:
1.内存中存储的是补码。

为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统
一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程
是相同的,不需要额外的硬件电路。

2.在内存中用一般用十六进制来表示的。

因为1个十六进制位可以表示4个二进制位,那在内存查看变量时,只用看(32 /4)8位即可,方便查看。
在这里插入图片描述

3.内存中补码是倒着存储的。
在这里插入图片描述

二.数据类型详细介绍

C语言有哪些基本数据类型?

char         //字符数据类型,      1个字节大小(以64平台为例)
short        //短整型             2个字节大小
int          //整型               4个字节大小
long long    //更长的整型         8个字节大小
float        //单精度浮点型       4个字节大小
double       //双进度浮点型       8个字节大小
char*        //字符型指针类型     8个字节大小
int*         //整型指针类型       8个字节大小
double       //双精度型指针类型   8个字节大小

1.1 类型的基本归类

整型家族

char
    unsigned char
    signed char
short
    unsigned short
    signed short
int 
    unsigned int 
    signed int
long
    signed long
    unsigned long
long long
    unsigned long long
    unsigned long long

为什么char类型属于整型呢?
因为字符存储的时候,存储的是ASCII码值,是整型,所以在归类位整型家族

浮点型家族:

float
long float
double
long double

指针类型家族:

int*
char*
float*
double*
void*

空类型:

void //表示空类型(无类型)
     //一般用于函数的返回类型,函数的参数,指针类型

构造类型:(自定义类型和变量)

数组类型 类型 数组名[]
结构体类型 struct
枚举类型 enum
联合类型 union

1.2认识有无符号的区别( signed 和 unsigned )

对于整型家族的类型来说,有符号和无符号是由区别的,不同的的编译器识别在区分char时也所有不同,有些是定义成signed
char,有些是定义成unsigned char; char 在VS2019上是 signed char。但是可以确定的是,

short == signed short;int ==signed int等

signed char 和 unsigned char的区别是什么呢?

signed char

我们知道char类型是一个字节(8个比特位)
假设它的二进制位是:01010111
首位就是它的符号位。
下图是八个比特位存放在二进制中的所有可能,因为首位是符号位,所以我们由此可知,
sigened char的取值范围是 -128 ~ 127而且只会在这个范围,超出的部分进行下一次循环。
在这里插入图片描述
在这里插入图片描述

unsigned char

二进制的每一位都是数值位,没有符号位。
假设unsigned char的二进制位是:10010101
则unsigned char八比特位二进制位取值范围:0 ~ 255
同理,即使数值递增也不会超出这个范围,超出部分进行下一次循环。

在这里插入图片描述

在这里插入图片描述

理解到这里,我们看些代码强化一下,尤其注意无符号类型(unsigned)。

1.3代码理解一:

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

char a变量值的是-1;先用32位二进制位求出补码。
原码:10000000000000000000000000000001
反码:11111111111111111111111111111110
补码:11111111111111111111111111111111
但因为是存放char类型中,只能取8个bite位,从补码后面截断8个比特位,得到11111111
但是用%d(十进制的形式打印有符号整型整数)形式来打印,所以发生了整型提升(a是有符号类型,所以用符号位的数补全二进制位32位数)得到:
补码:11111111111111111111111111111111
反码: 11111111111111111111111111111110
原码:10000000000000000000000000000001
所以char a的输出值应该是 - 1;
同理,因为char == signed char,char a 和 signed char b是一样的,所以 signed char b的输出值也应该是 -1;

unsigned char c的变量值是 - 1;先用32位二进制位求出补码。
原码:10000000000000000000000000000001
反码:11111111111111111111111111111110
补码:11111111111111111111111111111111
但因为是存放char类型中,只能取8个bite位,从补码后面截断8个比特位,得到11111111
重点在这里!!!因为c变量是unsigned char,不考虑符号位,全是数值位,在发生整形提升时,统统补0就好了。所以得到:
补码:00000000000000000000000011111111
符号位是0,说明是正数,所以反码,原码相同,输出的值应该是255.
在这里插入图片描述

1.4代码二理解:


int main()
{
    
    
	char a = -128;
	printf("%d\n", a);
	printf("%u\n", a);
	return 0;
}

先求出 -128的补码:
原码:10000000000000000000000010000000
反码:11111111111111111111111101111111
补码:11111111111111111111111110000000
因为是char 类型,只能存放后面8bite位,发生截断得到:10000000
又因为是char类型,补的是符号位,得到:11111111111111111111111110000000。
如果使用%d打印,符号位是1,是负数,补码需要转变成原码:最后的结果自然是 -128;
如果使用%u(十进制的形式打印无符号整型整数)打印,%u没有符号位的概念,所有二进制位都是数值位则打印的就是:11111111111111111111111110000000
这数字很大,你忍一下。打印的是:4294967168
在这里插入图片描述

1.5代码三理解:

#include<stdio.h>
#include<Window.h>
int main()
{
    
    
	unsigned int i;
	for(i = 9; i >= 0; i--)
	{
    
    
		printf("%u\n", i);
		Sleep(1000);//减慢打印时间
		            //单位是毫秒
	}

	return 0;
}

其实很容易看出代码死循环了,因为unigned int 是无符号整型恒大于等于0,跳不出for循环。当 i = -1时,原本要跳出了的,但因为是无符号类型,负1的补码是:
11111111111111111111111111111111。所以打印的是一个很大的数。
在这里插入图片描述

1.6代码四理解:

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

先看for的条件的条件,i从0递增至1000次
,再看a[ i ]的赋值。
-1,-2,-3,-4,-5,-6,-7…-998,-999。
但是前面我们说了,char的取值范围是-128 ~ 127。多出的部分进行下一次循环。
所以应该是如图:
在这里插入图片描述
strlen函数测的是字符串长度,判断结束的条件是遇见’0’;所以strlen在遇到第256个数字0时,就结束了判断。所以最终的输出应该是255;
在这里插入图片描述

1.7代码五理解:

#include <stdio.h>

unsigned char i = 0;//0~255
int main()
{
    
    
for (i = 0; i <= 255; i++)
       {
    
    
          	printf("hello world\n");
       }
	return 0;
}

unsigned char 的取值范围是0 ~ 255
所以是死循环了。

在这里插入图片描述

三. 大小端字节序介绍及判断

什么大端小端?

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

为什么有大端和小端:

为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32bit的long型(要看具体的编器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。 例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式

简而言之,变量在内存中存放的字节序(以字节为单位来讨论存储的顺序)在不同编译器上不同,有些编译器是小端字节序存储,反之是大端字节序存储。小端字节序存储是把一个数据的低位字节的内容,存放在低地址处,把一个数据的高位字节的内容,存放在高地址处。
大端字节序存储是把一个数据的低位字节的内容,存放在高地址处,把一个数据的高位字节的内容,存放在低地址处。

举个简单例子:
在这里插入图片描述
你可以写个程序来判断你的编译器的大小端

int main()
{
    
    
	int a = 1;//整型存放四个字节 00 00 00 01
	          //用字符p来接收整型的第一个字节
	char* p = (char*)&a;//a强制类型转换后取得一个字节的内容
	if (*p == 1)        //用1 或 0判断段大小端存放
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

在这里插入图片描述
我用的时VS2019,是小端字节序存储。
在这里插入图片描述

小编愚钝,如果有错误的地方请在评论区批评指出,看官走的时候给我个赞赞支持一下呗,谢谢。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/LHY537200/article/details/130909236