C语言篇 - 数据在内存中的存储

Posted IT莫扎特

tags:

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

一、基本数据类型介绍

1.1类型的意义:

1、使用这个类型开辟内存空间的大小(大小决定了使用范围)

假设我们在内存中定义了两个变量,A变量是int类型,B变量是char类型,我们都知道int类型是4字节所以他在内存中占4个字节的空间,float类型是4字节,所以他在内存中占用4字节的空间

2、如何看待内存空间的视角

类型就决定了定义变量时他们的大小变量A是四字节,变量B也是四字节,站在int型的角度他的存取都是整数,所以他是根据整数的形式存取的,而站在float类型的角度,他存放的是实数,所以他的存取方式是按实数的方式存取的,不同的数据类型就决定了他们在内存中存取方式是如何的,

1.2类型的基本归类:

注意:在这里是把char类型划分到整形家族里面去的,为什么char类型要划分到整形家族呢?是因为char是用ASCII码来表示的,而ASCII码值又都是十进制的整形

在这里首先了解一下char类型

int main()
{
	signed short int a = 0;//有符号短整型
	unsigned short b = 0;//无符号短整型
	char a;//有符号?还是无符号
	return 0;
}

char a是有符号的还是无符号的问题,取决的是编译器

1.2.1关于char类型有符号和无符号类型的区别

int main()
{
	unsigned char c1 = 255;
	signed char c2 = 255;
	printf("%d\\n",c1);//255
	printf("%d\\n",c2); //-1
	return 0;
}

char类型是占1字节的8个比特位,如果他是无符号类型呢,那么他的符号位也是有效位,而取值范围可以到255,再对比一下有符号char类型(signed
char),首先255对应的二进制序列是11111111(8个1)

最终其实是得到的二进制序列是对应十进制的-1,而他的最高位代表的是符号位-1表示负数,0表示正数

再来罗列他的取值范围

因为这里写的是 signed char所以他的最高位为0的化表示的是一个正数,那么他的源、反、补都是一样的而第一个二进制序列表示的是0,再看他的最后一个二进制序列,因为signed char的最高位是1表示的是负数,那么就需要对他的补码-1取反得到源码,那么就会是-1,而-127又是通过下面的计算过程的到的源码对应的二进制序列因为是有符号的所以他的最高位表示的是符号位,而剩下的7位才是它的有效位,而中间的10000000表示的是-128

那如果是无符号的char那么它的二进制序列对应的每一位就都是有效位,所以它的最大范围是0 ~ 255,既然我们知道了char类型的取值范围怎么运算的规则,是不是就可以知道其他数据类型的取值范围了?这里就不再继续了

总结:
1、signed char的取值范围是 0 ~ 127 | -128 ~ -1
2、unsigned char的取值范围是0 ~ 255

 
	short  == signed short
	long   == signed long
	int    == signed int    

而关于short - int - long这3中数据类型都是有符号的,这是C语言标准规定的,而如果想定义一个无符号整形就需要在前面加unsigned

1.3浮点数家族:


关于浮点型在后面的存储方式再详细介绍,这里简单了解两点
1、float类型称为单精度,占4字节空间 ,数值范围为3.4E-38到3.4E+38, 有效数字6 ~ 7位
2、double类型称为双精度,占8字节空间,数值范围为1.7E-308到1.7E+308, 有效数字15 ~ 16位

1.4构造类型:

构造类型也成为自定义类型

数组

数组是有类型的,数组的类型取决于数组的元素类型和数组的元素个数

int main()
{
	int arr[10] = {0};
	int a = 0;
	printf("%d\\n",sizeof(int));//4  求出int类型所占字节
	printf("%d\\n",sizeof(a));//4	求出变量a所占字节
	
	printf("%d\\n",sizeof(arr));//40   求出整个数组所占字节
	printf("%d\\n",sizeof(int [10]));//40  求出数组类型所占字节

	//int arr1[5];  
	return 0;
}

那么 int arr1[5]; 是不是数组类型呢?答案是的,因为数组也是自定义类型,数组元素个数和数组元素类型决定了数组是什么类型

1.5指针类型

1.6空类型:


void参数列表声明

void func(void)//表示不需要传递参数
{
	printf("hehe\\n");
}

int main()
{
	func(100);
	return 0;
}

虽然程序不报错,但是显示了一条警告,这也是不太好的,如果指定不需要参数,那么就不传参数,否则就不需要指定void

二、整形在内存中的存储

我们之前讲过一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的

那接下来我们谈谈数据在所开辟内存中到底是如何存储的

我们知道为 a 分配四个字节的空间。 那如何存储?

下来了解下面的概念:

1.1流程图

1.2概念总结

正数的原、反、补码都相同。
对于整数来说:数据存放内存中其实存放的是补码。

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


很明显这个结果不是想要的,那么是不是就说明了使用原码不合适呢

紧接着再使用补码的方式去计算,这里注意正数的原、反、补都是一样的,但是是不是就说明了正数是按照原码的方式去存储的呢?不是的,虽然正数的原、反、补一样,但是在内存中的存储方式却不是一样

但是一个整形只能存放32位,多出来的一位是不是就得截断啊 实际上存的还是数值0,使用补码的方式计算,在这里既处理了符号位又处理了有效数值位

1.3实践总结

数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理

1.4大小端

科普知识:为什么会有大端小端呢

**

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

**

其实数据在内存中是以低地址存放在低地址处,高地址存放在高地址处

为什么要这么说呢?首先我们可以看出的是这两个箭头指向的是不同的两个地址,而这两个地址中间恰好是差四个字节 44 33 22 11这块内存空间被0x00D5F93C标识着,那么往后推演,是不是就能用0x00D5F93C表示44这个字节的内存,0x00D5F93D表示33这个字节的内存,0x00D5F93E表示22这个字节的内存,0x00D5F940表示11这个字节的内存,在这个推演的过程就能看到地址是逐渐递增的,并且低地址处的空间被低地址处指向,而高地址处空间被高地址处指向

以上就是小端存储

而大端存储呢恰好与小端存储相反

高地址处存放低地址,低地址处存放高地址

1.5大小端总结:

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

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

补充:

字节序:在内存中以字节存储的顺序

1.6判断机器是否是大小端


解题思路:想要判断机器是大端还是小端,就得先知道它的第一个字节存放的内容是什么,如果存放的是高地址处的内容,那么他就是大端,但是如果存放的是低地址处的内容那么就是小端

此刻编译器采用的是小端存储,而若想把这第一个字节给挑出来,就需要使用指针变量访问内存,在这里变量a是占4个字节的空间的,变量a是以4个字节角度看待这块内存的,若想只挑出第一个字节的内容,最合适的就是char了,操作内存当然得使用指针了,那么char*就最合适不过了

方案一:

#include <stdio.h>

int Check_Sys() 
{
	int a = 1;
	char *p = (char*)&a;//指针指向内存中的第一个字节处地址
	if (*p == 1) 
	{
		return 1;
	}
	else 
	{
		return 0;
	}
}

int main()
{
	int ret = Check_Sys();
	if (ret) 
	{
		printf("小端\\n");
	}
	else 
	{
		printf("大端\\n");
	}
	return 0;
}

方案二:

#include <stdio.h>

int Check_Sys() 
{
	int a = 1;
	char *p = (char*)&a;
	return *p;//返回指针所指向的内容
}

int main()
{
	int ret = Check_Sys();
	if (ret) 
	{
		printf("小端\\n");
	}
	else 
	{
		printf("大端\\n");
	}
	return 0;
}

方案三:

#include <stdio.h>

int Check_Sys() 
{
	int a = 1;
	return *((char*)&a);//对指针解引用,返回指针所指向的内容
}

int main()
{
	int ret = Check_Sys();
	if (ret) 
	{
		printf("小端\\n");
	}
	else 
	{
		printf("大端\\n");
	}
	return 0;
}

三、练习

//输出什么?

#include <stdio.h>
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,-1,255
解题思路:首先我们得知道-1在内存中存放的是补码,那么需要对-1的原码取反+1得到补码,再将-1存放到a这个变量里,而变量a是一个字符类型,char类型占8个比特位所以会只保留低位的二进制序列,这个时候就会发生截断会只保留二进制序列低位的8个比特位

而在打印的时候会整形提升,如果是有符号的char类型,在高位就会补符号位这里的符号位是1,再对32个全1通过计算得出它的原码,而原码就是变量a的值

有了这个例子那么signed char也是有符号的数据类型,就不再解释
再来看看unsigned char,为什么变量c = -1最终打印出来的值会是255呢?

同样的道理先求出-1的补码,因为是无符号类型,所以它的最高位代表的并不是正负,它的32个1全都是有效数字,但是由于而变量c是一个字符类型,char类型占8个比特位所以会只保留低位的二进制序列,这个时候就会发生截断会只保留二进制序列低位的8个比特位
在打印的时候会发生整形提升,因为是无符号类型所以在整形提升的时候高位补0,而最终打印的结果就是255

#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\\n",a);
    return 0; }

同样的道理还是先求出变量a 的补码,经过一系列计算后-128的补码

在高位被截断的时候,变量a中存放的还是低位的8个比特位10000000
而在整形提升的时候,由于char是有符号类型,所以会在高位补符号位,得到后的补码就是11111111111111111111111110000000,当以无符号整型打印的时候就是通过它的补码方式表现的

#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\\n",a);
    return 0; }

在这里我们可以观察到-128的和正128的低8位都是相同的,10000000,所以在被截断后存放在变量a中的补码也是一样的,再通过整形提升,由于是char类型的所以会在高位补符号位 1,再将结果打印
我们可以看到没啥区别

int i= -20;
unsigned  int  j = 10;
printf("%d\\n", i+j); 
//按照补码的形式进行运算,最后格式化成为有符号整数

再看看这个代码
答案是-10,以下就是计算过程

unsigned int i;
for(i = 9; i >= 0; i--) {
    printf("%u\\n",i);
}

这个结果会是什么呢
死循环吧!
我们可以看到先是打印了9 ~0然后进入了死循环,会什么呢,在变量i–后i变量的值等于0时,再减减不就等于-1了吗而-1在内存中的补码是32个全1,所以范围很大,但是你觉到如果打印到0的时候是不是条件还满足,i–,i的值又成了-1,于是又进入了下一个死循环

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

答案是255从-1开始的话它的第255位就是 00000000这个二进制序列,而这个值表示的是’\\0‘,所在在strlen遇到它的时候就会结束计数,第0位表示的就是斜杠零

#include <stdio.h>
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\\n");
   }
    return 0;
}

这个代码的结果又是什么呢?
还是死循环吧!变量i从0开始一直递增直到内存中的补码变成了11111111,再++的话是不是就会溢出了,最终只保留它的低8位,结果还是0,这时候条件还是满足,有一次循环,那么不就进入了死循环吗


浮点型在内存中的存储

常见的浮点数:

3.14159
1E10
浮点数家族包括: float、double、long double 类型。 浮点数表示的范围:float.h中定义 浮点数存储的例子:

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

你能知道这个代码打印的结果是什么吗

先了解知识理论再回头来看

小数点的后一位表示的是2的负1次方,后两位表示2的负2次方,101.1写成科学计数法就是1.011 * 2^2,当我们换算到这个公式里头来就是
(-1)^0 * 1.011 * 2^2 这里的
S = 0
M = 1.011
E = 2

------------如果是9.0呢
(-1)^0 * 1.001 * 2^3
S = 0
M = 1.011
E = 3



总结:
1、如果是float类型,这个中间数是127。比如,2^10的E 是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
2、如果是double类型,这个中间数是1023。比如,2^10的E 是10,所以保存成32位浮点数时,必须保存成10+1023=1033,即010000001001。

浮点数的存

下一个话题浮点数以二进制的形式在内存中存储,

int main()
{
	float a = 5.5f;
	//(-1)^0 * 1.011 * 2^2
	//S = 0
	//M = 1.011
	//E = 2
	//因为是浮点型中间数是127,而E又是2
	//E = 2 + 127 = 129  
	//对应的二进制序列:01000000 10110000 00000000 00000000
	//对应的十六进制序列:40       B0		00      00
	return 0;
}

对应的二进制序列内存块

把这32位二进制序列转换成16进制就是 40 B0 00 00

由于使用的是VS2017编译器采用的是小端存储,所以低地址处存放的是二进制序列低位的数据,而高地址处的是二进制序列高位的数据

浮点数的取

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

回到之前的那个问题

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

当我们把以上的知识弄明白之后,对于这个问题只要把它的二进制序列求出来,再截取它的6有效位数(因为浮点型的有效位数是6位有效数字) 所以会截取0.000000


解析这两句代码的意义

*pFloat = 9.0;

printf(“num的值为:%d\\n”,n);
所以它的二进制序列是== 01000001000100000000000000000000==当以%d的形式打印,而他的最高为0所以是正数,而正数的原、反、补相同所以打印的结果是

以上讲解就到这里,谢谢大家

以上是关于C语言篇 - 数据在内存中的存储的主要内容,如果未能解决你的问题,请参考以下文章

看了这篇博客,再也不害怕别人问我把不把握得住数据存储了

看了这篇博客,再也不害怕别人问我把不把握得住数据存储了(初)

C语言进阶学习笔记一数据的存储(总结篇+思维导图+浮点型部分内容)

c语言中字符型数据在内存中的存储形式是?

C语言进阶——数据在内存中的存储

C语言进阶——数据在内存中的存储