数据在内存中的存储

x33g5p2x  于2021-11-22 转载在 其他  
字(6.4k)|赞(0)|评价(0)|浏览(204)

1.数据类型介绍:

类型的意义

1.类型的使用决定了决定了类型开辟内存空间的大小其大小决定了存储数据的范围

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

类型长度
char1个字节
int 4个字节
short2个字节
double 8个字节
float4个字节
long 4个字节
long long8个字节

l

验证这些类型的长度

#include<stdio.h>
int mian()
{
	printf("%d\n", sizeof(char));
	printf("%d\n", sizeof(int ));
	printf("%d", sizeof(short));
	printf("%d", sizeof(double));
	printf("%d", sizeof(float));
	printf("%d", sizeof(long));
	printf("%d", sizeof(long long));

}

结果如下

值得注意的是sizeof(long )的大小在linux平台下是8个字节

2.整形家族
1.char

--------------有符号char ->signed char 

--------------无符号char ->unsigned char 

2.int 

---------------有符号整形 ->signed int  

---------------无符号整型->unsigned int 

3.long 

---------------长整型->long (int)

---------------无符号长整型->unsigned long (int )

4.short 

----------------有符号短整型->signed short(int )

----------------无符号短整型->unsigned short(int )

5 long long 

----------------更长的长整型->long long

3.浮点型家族

double

float

4.自定义类型

1.结构体 struct

2.联合体 union

3.枚举     enum

指针类型

1.整形指针 ---------------->int *

2字符指针------------------>char *

3 float .double * short指针

4.void*指针又被称为万能指针.空类型,void不能够定义变量也就不存在void a;这种写法

一般用于函数的返回类型和函数的参数和void*

而void可以定义变量,void可以接收任意类型的指针也被称为万能指针。但在解引用或者加减整数的时候必须将其强转为确定的类型,如果不进行强转无法进行解引用和加减整数的操作

二整型在内存中的存储

1.变量的创建要在内存中开辟空间而开辟空间的大小是由变量的类型决定的
计算机符号数中有三种表示方法。原码,反码,补码三种方法都有符号位和数值位和符号位

正数的原码,反码,补码都相同,原码只需要将该数按照正负数的形式翻译成二进制即可。负数则需要计算才能得出。负数的反码等于原码的符号位不变其他位位按位取反,补码则只需要将反码加1即可得到补码。计算机中存储的是数据的补码

符号用0来表示正,用1来表示负

2.无符号数
无符号的原码,反码,和补码都相同这一点和正数是一样的

下面我们来看几个例子

#include<stdio.h>
int main()
{
	int a = -1;
	unsigned int b = -1;
	printf("%d\n", a);
	printf("%d ", b);

}

运行结构如下:

我们可以看到结果是 -1,-1.有人可能会问了b他不是一个无符号整型吗怎么会 是负数了遇到这种情况不要慌张仔细分析一下就可以了。数据在内存中存的数据的补码。打印的数据的数据的原码

我们可以看到a和b中存的都是ff ff ff ff.我没知道1个16进制数字相当于4个比特位1个字节相当于8个比特位所以了11111111111111111111111111111111用16进制来表示就是ff ff ff ff 这也就说明了在整数中内存中的存储方式与类型无关。该怎么存就怎么存,我们进一步看。代码中要将a,b以%d的形式打印%d是按照有符号整型的形式打印。此时内存中放的是补码,打印的是原码。我们只需要将补码加1,符号位不变其他位按位取反即可得到原码。由于%d是按照有符号整型的方式打印,所以编译器会认为b中的最高位是符号位。在按照上述方法就可以得出答案。这其实是看问题的视角不同,我认为你是一个有符号数,你就是个有符号数。我认为你是个无符号数你就是个无符号数。

若将%d改成%u那么答案将会截然不同

#include<stdio.h>
int main()
{

	unsigned int b = -1;
	printf("%u", b);
}

可以发现结果截然不同这是因为当以%u的形式进行打印时,那么编译器就不会在认为最高位是负号位了此时每一位都是有效位 。此时打印出来的数字会是一个很大的数字

三。大小端

  1. 地址有高地址和低地址之分
  2. 数据也有高位和低位之分
  3. 什么是大端什么是小端了
      大端模式,是指据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

个人的理解是小端模式就是数据的高位在高地址处数据的低位在低地址处,大端模式正好和小端模式相反

#include<stdio.h>
int main()
{
int a=16;
}

我们可以看到内存中存的是 10 00 00 00.16如果将他用十六进制形式来表示应该 00 00 00 01

而我们看到计算机中是倒着存的

说明此时此时计算机是小端存储模式即数据的高位在高地址处数据的低位在低地址处

百度曾经有过一道面试题,要我们写一个程序判断当前机器是大端存储模式还是小段存储模式

通过以下代码我们就可以判断了

#include<stdio.h>
int main()
{
	int a = 1;
	char *p = (char *)(&a);
	if (*p == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端");
	}

}

如果机器是小端存储模式那么在内存中应该是这样存储的

我们只要将第一个地址取出来如果他是1那么他就是小端存储模式否则就是大端所以我们可以将a的

地址取出来拿出他的第一个字节看他是不是1即可。

运行结果如下:

四:整型提升

1.什么是整型提升

整型提升的意义在于:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。 [1]

C语言标准中仅规定了:

char的长度 ≤ short int的长度 ≤ int的长度

这意味着short int与int的长度相等的可能。这种情形下,unsigned short就无法提升为int表示,只能提升为unsigned int。

2.如何进行整型提升

当数据的类型小于int时参与运算需要进行整型提升,那又是怎么整型提升的了。整数提升是根据变量的类型和根据类型的最高位进行题升。举个例子:

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

首先-1是一个整型有4个字节而a的类型是char 只有一个字节。显然无法存下去。-1在内存中存的是11111111111111111111111111111111上面已经说过了就不说是怎么来的了那么这时这个2进制要放到a里面去。但是a只有一个字节只能存8个比特位,这是编译器会把这个二进制低位的8个比特位存进a中,a中存的就是11111111,然后将a打印由于a时一个char类型的所以在打印的时候需要整型提升。提升的规则按照a的最高位进行提升。编译器一看a时一个有符号char ,最高位为1.所以编译器会按照最高位进行提升提升后的结果为11111111111111111111111111111111但这是补码打印的时候打印的是补码。所以我们要求出原码在进行打印。这个二进制序列-1得到补码。补码为11111111111111111111111111111110,符号位不变其他位按位取反就得到了原码:10000000000000000000000000000001;读出来就是-1;所以打印的是-1;

运行结果如下:

下面我们再来看一个例子

#include<stdio.h>
int main()
{
    char a=128;
    printf("%d",a);
}

很多人第一次看到这一个代码的时候会认为这不是很简单吗?很简单啊128我一开时看到这个代码的时候也是这么认为的但实际的结果却是

很多小伙伴可能看到编译器的运行结果时,还不敢相信。这是为什么了。128是一个整数有4个字节 但a是一个char 只有1个字节只能够存一个字节.128的对应的原码为00000000000000000000000010000000由于128是正数所以原码和补码相同所以a中存的是二进制序列为10000000,当进行打印的时候由于a的大小小于整型所以要先进行提升先提升为整型所以就被提升为11111111111111111111111110000000但这时候这是一个负数了所以我们要求出他的原码。求出来为100000000000000000000000010000000.也就是-128.

经过上的例子我们就可以很清楚的知道为什么有符号char的范围为-128到127.

面试题

#include<stdio.h>
int main()
{
    char arr[1000];
    for(int i=0;i<1000;i++)
    {
        arr[i]=-1-i;
    }
    printf("%d",strlen(arr));

}

结果为:

strlen是求字符串长度遇到‘\0'就结束了char的范围是-128到127.而’\0'的ASCLL为0.

所以结果为0

#include<stdio.h>
#include<cstring>
int main()
{
    int ch;
    while((ch=getchar())!=EOF)
    {
        putchar(ch);
    }

}

相信这个代码大家都十分的熟悉了,但值得注意的是如果我将int ch,该为unsigned char 那么这个程序将无法停下来由于getchar()返回的是一个整型,并且EOF的ASCLL为-1,而ch是无符号char是大于等于0的所以这个程序将永远不会停下来。

五。浮点型在内存中的存储

浮点数是如何在内存中存储的了。

根据国际标准IEEE(电气和电子工程协会)规定,任何一个浮点数NUM的二进制数可以写为:
NUM = (-1) ^ S * M * 2 ^ E;//(S表示符号,E表示阶乘,M表示有效数字)
①当S为0时,表示一个正数;当S为1时,表示一个负数
②M表示有效数字,1<= M <2
③2^E表示指数
比如十进制的5.5二进制就是101.1 就可以写成(-1)^ 0 1.0112^2
在比如十进制的-3.0,二进制就是-0011.0 就可以写成(-1)^ 1 * 1.1 * 2 ^ 1
而规定float类型有一个符号位(S),有8个指数位(E),和23个有效数字位(M)
double类型有一个符号位(S),有11个指数位(E),和52个有效数字位(M)
以float类型为例:

IEEE对于(有效数字)M和(指数)E有特殊的规定: (以float为例)
1.因为M的值一定是1<= M <2,所以它绝对可以写成1.xxxxxxx的形式,所以规定M在存储时舍去第一个1,只存储小数点之后的数字。这样做节省了空间,以float类型为例,就可以保存23位小数信息,加上舍去的1就可以用23位来表示24个有效的信息。
2.对于E(指数)E是一个无符号整数所以E的取值范围为(0~ 255),但是在计数中指数是可以为负的,所以规定在存入E时,在它原本的值上加上中间数(127),在使用时减去中间数(127),这样E的真正取值范围就成了(-127~128)。
对于E还分为三种情况:
①E不全为0,不全为1:
这时就用正常的计算规则,E的真实值就是E的字面值减去127(中间值),M的值要加上最前面的省去的1。
②E全为0
这时指数E等于1-127为真实值,M不在加上舍去的1,而是还原为0.xxxxxxxx小数。这样为了表示0,和一些很小的整数。
所以在进行浮点数与0的比较时,要注意。
③E全为1
当M全为0时,表示±无穷大(取决于符号位);当M不全为1时,表示这数不是一个数(NaN)

验证:

#include<stdio.h>
int main()
{
float f=5.5;
}

5.5按照上述方法可以写成101.1也就是-1^(0)1.0112^2此时s=0,E=10000001 M为01100000000000000000000

如果用十六进制来表示就是40b00000

这说明浮点型的存储也有大小端的区别只需将上述结果倒过来即可。

下面让我们来看一个例子

#include<stdio.h>
#include<cstring>
int main()
{
int n=9;
float*p=(float*)&n;
printf("%d\n",n);
printf("%f\n",*p);
*p=9.0;
printf("%d\n",n);
printf("%f",*p);

}

这段代码的输出结果是什么了。首先第一个printf打印的是9这肯定没有问题n是整型用%d来打印这肯定没有问题肯定是9.第二个printf打印的是什么了,p是个float的指针将n的地址强转为(float)。而9的在内存中是00000000000000000000000000001001这时候p解引用要访问这一块空间第一个0是S而后面8个比特位对应的值是E加上127对应的二进制。一看为0根据IEE标准。②E全为0
这时指数E等于1-127为真实值,M不在加上舍去的1,而是还原为0.xxxxxxxx小数。这样为了表示0,和一些很小的整数。所以计算结果就是-1^0
0.101*2^-126这是一个非常小的数字了所以为0.

p=9.0可表示为-1^01.001*2^3那么n在内存中对应的二进制为

0 10000010 00100000000000000000000所以在内存中是这样存的而是在是以%d打印编译器一看最高位是0是一个正数就直接把这个二进制读出来了,而最后一个printf你怎么存进去在在怎么拿出来结果肯定不会变故结果为9.000000

结果如下:

最后个人认为强制类型转换,并没有改变什么,只是改变了编译器读取这个二进制的方式。

如有错误请在评论区留言,谢谢!

相关文章

微信公众号

最新文章

更多