[C/C++]Misc:数据的存储方式

4 篇文章 0 订阅
本文详细讲解了整型(int、long、short、char)及其有符号/无符号的不同存储方式,包括原码、反码和补码,以及整型提升的概念。同时介绍了浮点型(如float和double)的IEEE 754标准存储结构,涉及符号位、指数和尾数的表示。文章还涉及了字节序(小端/大端)、结构体的存储规则和大小端对齐。
摘要由CSDN通过智能技术生成

1整型的存储方式

这里的整型指的是int,long,short,char以及相应的signed和unsinged类型。对于这些数据的存储,涉及的是原码,反码,补码还有整型提升(integer promotion)。

1.1三个码

通常我们cout或者printf(“%d”)时,我们看到的整型数字是由对应的原码组成的,而该码的具体内容由该数字的二进制形式表示。如-10的原码 = 10000000 00000000 00000000 00001010。

反码,就是原码的取反,但是如果原码的首位是符号位,则不改变符号位的值,于是-10的反码就是11111111 11111111 11111111 11110101。

补码就是在反码的基础上+1,即11111111 11111111 11111111 11110110。补码取反后(符号位不变)+1=原码。补码就是-10这个数字在内存中保存的样子(如果在VS环境下查看的话,是补码的16进制表示,且是小端字节序)。而CPU在运算时,用的就是补码。

实际上,原码反码补码是一个只针对负数的,有符号的一个概念,对于一个正数来说,原码就是他的补码。更深一点,由于CPU中只有加法器,不能执行1-1这样的操作,只能执行1+(-1)的操作,而补码的概念就类似与将负数转化成+(-1)的操作。我们考虑如下的情况:

请用原码补码计算10-10

10 = 00000000 00000000 00000000 00001010
因为10是正数,所以两个码一致。
-10 = 100000000 00000000 00000000 00001010
根据上文推导,-10的补码=11111111 11111111 11111111 11110110,两个补码相加得到:1 00000000 00000000 00000000 00000000。相加得到的东西似乎不等0,但实际上由于int是只有4个字节大小,即32个bits大小,所以需要截断最后相加的数据,从后往前数32然后丢弃剩下的就是最后的值,即00000000 00000000 00000000 00000000=0,于是CPU完成了这样的计算。

1.2整型提升

整型提升发生在当该数据类型不满足4个字节长度时,即当数据类型是char,short以及对应的unsigned, signed,然后又要求他们要像int这样处理。

我们结合以下代码来探讨。

#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;
}
//ps:char在大部分编译器下是signed char,int 则全是signed int

对于-1的补码=11111111 11111111 11111111 11111111,由于a,b和c是char类型数据(1字节),所以他们都只能存储a=b=c=11111111,但是我们在print的时候又将他们看做int,此时将会发生整型提升,提升的规则如下:

  1. 如果在提升前的数据类型是有符号的,则在前面补1。如,a和b,在打印时,内存将他们视为11111111 11111111 11111111 11111111,这是补码,翻译成原码就变成了-1。
  2. 如果在提升前的数据类型是没有符号的,则在前面补0。如c,内存将其视为00000000 00000000 00000000 11111111,因为是无符号的,所以原码跟补码一样,打印出来就是255。

1.3练习题

输出什么?

int main()
{
    char a = -128;
    cout<<(unsigned int)a<<endl;
    cout<<(int) a<<endl;
    return 0;
}

输出什么

int main()
{
    char a =128;
    cout<<(unsigned int)a<<end;
}

输出什么?

int main()
{
    int i = -20;
    unsigned j = 10;
    cout<<i+j;
    
    return 0;
}

2浮点型的存储方式

一下内容如果直接理解有困难,可以访问这个网站
https://www.h-schmidt.net/FloatConverter/IEEE754.html
你可以通过这个网站变看边做。

C/C++对于浮点型的存储遵循IEEE(电子和电子工程协会)754,任意一个2进制浮点数V可以表示成下面的形式:

  • (-1)^s*M*2^E
  • (-1)^s表示符号位,当s=0,V为正;s=1,V为负。
  • M为有效数字,[1,2)
  • 2^E表示指数位
  • 即只需要存储s,M,E三个数

此外,IEEE 754对有效数字M和指数E,还有一些规定。由于M总是以1.xxxx,1开头,所以只存储xxxx部分,所以实际上M处可以保存24为有效数字(float);至于E的存储规则细节不谈,结论是8bit要+中间值127,11bit+中间值1023。(注意这个结论只是为了方便使用,而且在精度要求不高的前提下是可行的,E的存储细节是区分三种不同浮点数的关键,你可以查看《CSAPP》了解)

依据这种规则:

float f = 5.5f->101.1->1.011*2^2;

则 s=0,M=011,E=2+127=129(E_r=2);

则内存中的存储为:0100 0000 1000 0000 0000 0000 0000 0011对应的16进制储存 40 b0 00 00,因为是小端字节序(等下会讲),所以实际上看到的是00 00 b0 40。

对于float来说,他占32bits,即4字节,他是如何存储s,M,E这三个数呢?在这里插入图片描述
而对于double,他占64个bits,存储方式如下所示:在这里插入图片描述
说了这么多不如直接上手感受一下,

#include<iostream>
using namespace std;
int main()
{
   int n=9;
   //将整数9以浮点型存入内存中
   float* pFloat = (float*)&n;
   
   cout<<(int)n<<endl;
   cout<<(float)*pFloat<<endl;

   *pFloat = 9.0;
   cout<<(int)n<<endl;
   cout<<(float)*pFloat<<endl;
   
   system("pause");
   return 0;
}

对于int n = 9, 来说,int是一个4字节的数据,跟float字节数一样,他的补码(就是他的原码) = 0 00000000 00000000000000000001001,注意到我这次不再是每8个空一下,我这里的空法是跟float的存储方式的图是保持一致的,当将9视为一个浮点数进行打印时,第一个0代表s,00000000为E,对应的实际E为-127,00000000000000000001001是M,则实际上他打印的是一个:(-1)^0*1.00000000000000000001001*2^(0-127)这个二进制对应的一个数,这个数在可以展示的精度下直接变成0.0000…。

而对于一个*pFloat=9.0来说,他的二进制表示为1001.0 = 1.001*2^3,此时,s=0,E=3+127=1000 0010,M=001, 0 1000 0010 00000000000000000000001 = *pFloat,要求他以整数形式输出(因为是正数,所以不用原码补码的转换),则输出的就是1,090,519,041。

2.1习题

这部分没习题,因为我搞不来小数点后面的二进制。

3大小端字节序

编译器在安排内存的存储时,将数据倒着存(小端字节序)。比如,int a = 0x11223344,大端字节序为11223344,把数据的低位字节序的内容放在高地址处,高位字节序内容放低地址处;小端字节序为44332211。这个你们可以在VS这个IDE中查看,而对于不能查看的,请完成下面的一个练习。

设计一个小程序判断当前机器的字节序。

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    char *pa = (char *)&a;
    if (*pa == 1)
    {
        cout << "s" << endl;
    }
    else
    {
        cout << "l" << endl;
    }
    system("pause");
    return 0;
}

4 结构体的存储

结构体的存储涉及到offset,偏移量以及对齐的概念。

struct A
{
	char a;
	int b;
	int c;
};

存储规则如下:

  1. 对于第一个数据成员,他永远放在偏移量为0的地方
  2. 对于其他数据成员,他会在偏移量为他的对齐数处存放。
    • 对齐数 = Min{sizeof(该数据成员类型),编译器默认的对齐数(VS是8)}
  3. 存放完所有数据后,结构体的大小为数据成员中最大的对齐数的最小整数倍(当然是大于里面所有数据所占内存大小)
    在这里插入图片描述
    这是另一个例子,在这个例子中我们假设运行再32位机子上,则
struct foo{
	int a;// at 0-3
	char b;// at 4
	short c;// at 6-7
	char *d;// at 8 - 11
	char e;//12
};

12 - 0 + 1 = 13 ,不是其中最大的对齐数(4,int)的整数倍,所以额外的14-15这两个空间对齐进来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值