C语言中各类型的内存存储

目录

整型家族

浮点型家族:

构造类型(自定义类型):

指针类型:

空类型:

整型的内存存储

原码 反码 补码

大小端字节序

有关字节存储的相关练习:

整型截断

整型提升

浮点类型在内存中的存储

小数的二进制表示方式:IEEE 754

        指数E取出的三种情况:


目前已经学了的数据类型:

  • char——1
  • short——2
  • int——4(在16位电脑是2个字节,32位电脑是4个字节)
  • long(sizeof(long)≥sizeof(int))
  • long long——8
  • float——4
  • double——8

其中还有布尔类型:(但是布尔类型在c99中才引入)

#include <stdio.h>
int main()
{
    _Bool flag=true;
    if(flag)
    {
        printf("hehe\n");
    }
    return 0;
}

要进行内存存储的学习,我们要先把学过的类型做以下分类讨论:

整型家族

 

整型家族中的每个类型又分为有无符号类型,而以下的类型都默认为:

  • int===>signed int
  • long===>signed long
  • short===>signed short

但char≠signed char,要取决于编译器,常见的编译器下都是char===》signed char

我们都知道无符号数不能存储有符号的数字,但请看下面的代码:

#include <stdio.h>
int main()

{
    unsigned int num=10;
    printf("%d\n",num);
    num=-10;
    printf("%d\n",num);
}

此时输出的依然是10     -10,为什么呢?

是因为我们用有符号数的方式打印出来了无符号数,且把有符号数字赋给了无符号数,此时只能打印出有符号了。

unsigned int也是四个字节存储空间,于是使用%d的时候就可以用有符号数的形式输出了。

应该改为:

#include <stdio.h>
int main()
{
    unsigned int num=10;
    printf("%u\n",num);
    num=-10;
    printf("%u\n",num);
}

此时的输出结果才正确:

 

为什么会有这么大的差异?我们这里先保有一些疑惑。

浮点型家族:

  • float——4
  • double——8

构造类型(自定义类型):

  • 数组类型
  • 结构体类型 struct
  • 枚举类型 enum
  • 联合类型 union

指针类型:

  • int*
  • char*
  • float*
  • void*

空类型:

  • void

 

整型的内存存储

输入下列代码并按下f10运行后:

#include <stdio.h>
int main()
{
    int a=10;
    int b=-10;
    return 0;
}

在调试器的内存窗口中我们可以看到如下:

 

我们都知道a是用四个字节空间存储的,那么a是以怎样的方式进行存储的呢?

原码 反码 补码

整数在计算机中都是以二进制形式的原码,反码,补码的方式表示的。

三种表示方法都有符号位和数值位的概念,最高位上若为0,则表示为正数,若最高位上为1,则为负数。

内存中是以二进制的补码来存储的。

原码:二进制原本的样子

反码:将符号位保留,数值位上的其他位数全部取反

补码:在反码基础上将最低位+1,便得到补码。

Tip:在这里要注意反码与学习的“~”按位取反操作符不同,按位取反是将二进制(包括符号位)上的所以位置都取反,而反码是符号位不变,其他为按位取反。)

而正数与负数的原、反、补码并不相同:

Q:整数为什么在内存中以补码的形式存在?

计算器中只有加法器。

计算器中是无法计算1-1的,而只能用1+(-1)的方式来计算。

下面让我们来验证此时整型在运算中是以原、反、补码哪种方式进行运算的。

若为原码:

若为补码:

 

 通过验证,我们可以知道运用补码运算才是正确的结果,而原、补码的转换也相对容易:原码→补码(取反、+1) 补码→原码(也是取反、+1)

所以,计算机中整型以补码形式存在并进行运算。


   创造一个变量int a=0x11223344,运行后打开编译器的内存窗口,我们会发现内存窗口中并不是以11223344的顺序存储的,这是为什么呢?这就相关大小端字节序的问题了。

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

 

大小端字节序

在我们创造一个变量int a=0x11223344时,打开我们的内存窗口,会发现并不是以11223344的顺序存储的,这是为什么呢?这就相关大小端字节序的问题了。

 

大端字节序存储:低字节的数据存放在高地址处,高字节的数据存放在低地址处。

小端字节序存储:高字节的数据存放在高地址处,低字节的数据存放在低地址处。

Q:为什么会有大小端字节序之分?(以下来源于百度出的内容)

作者:starmier

链接:为什么会有大小端模式之分呢? - 简书

来源:简书

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为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处理器还可以由硬件来选择是大端模式还是小端模式。

在搜索大小端字节序相关知识的时候,偶然发现了一道十分有趣的题目,想在这里记录下:

百度神奇题目:简述大小端字节序的概念,并写一个小程序来判断大小字节序。

方法一:
#include <stdio.h>
int main()
{
  int a = 1;
  char* p = (char*)&a;
  if (1 == *p)
  {
    printf("小端");
  }
  else
  {
    printf("大端");
  }
  return 0;
}
方法二(方法一改良):
#include <stdio.h>
int main()
{
  int a = 1;
  if (*(char*)&a)
  {
    printf("小端");
  }
  else
  {
    printf("大端");
  }
  return 0;
}

Q:char类型有字节序么?

A:无,因为char只有一个字节大小,而字节序表示的是字节的排列顺序,所以char类型没有字节序。

有关字节存储的相关练习:

but 在做题前先认识两个东西:整型提升与整型截断。

整型截断

比如说char占1个字节,int 占4个字节,当强制将int 赋给char时就会截短,只将4字节的最后的一个字节给char
举例的话,譬如:
int a = 305419896;
char b = a;
a转换成2进制是:0001 0010 0011 0100 0101 0110 0111 1000 他的16进制是12 34 56 78,
那么b就是a的最后一个byte,即b的二进制表示是0111 1000,16进制是78,十进制是120.
就是这么样的一个转换和截断。

整型提升

若原本整型家族的类型中的一个变量所占的字节比int少,那么在以int形式使用时要发生整型提升。

整型提升的规则:

  • 若为无符号数,则在最高位上补齐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;
}

 

 


//输出结果是什么?
#include <stdio.h>
int main()
{
    char a=-128;
    printf("%u\n",a);
    return 0;
}


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


#include <stdio.h>
int main()
{
    int i=-20;
    unsigned int j=10;
    printf("%d\n",i+j);
    return 0;
}


#include <stdio.h>
int main()
{
    unsigned int i;
    for(i=9;i>=0;i--)
    {
        printf("%u\n",i);
    }
    return 0;
}

最终输出结果:

 


再来看下面代码的输出结果: 

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

最终输出结果:

 为什么这里会最终输出255呢?这里便涉及了char类型变量能存储的数值范围。

探讨:一个char类型的变量到底能存放什么数值?

unsigned char:从0到255后再回到0

signed char:从0到128到-1到-127再回到0

 

 

**Tip:**当我们遇到10000000时直接解析为128,因为就算我们将原码转为补码后也依然会是10000000

在这里推荐一个头文件:<limits.h>

头文件:#include <limits.h>点开里面可以查各种类型的取值范围

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

 


浮点类型在内存中的存储

常见浮点类型:

3.14159          1E10(代表1×10的-10次方)

浮点数表示范围可以用<float.h>打开来看

#incldue <stdio.h>
int main()
{
    int n=9;
    float* pFloat =(float*)&n;
    printf("n的值为:%f\n",*pFloat);
    printf("*pFloat的值为:%f\n",*pFloat);
    *pFloat=9.0;
    printf("num的值为:%d\n",n);
    printf(""*pFloat的值为:%f\n",*pFloat);
    return 0;
}

(无论是%f还是%lf默认都是由小数点后6个0,编译器决定的)

小数的二进制表示方式:IEEE 754

根据国际标准IEEE(电气电子工程师学会)754,任意一个二进制浮点数可以表示成下面的形式:

  • (-1)^SM2^E
  • (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数
  • M表示为有效数字,大于等于1,小于2
  • 2^E表示指数位

现在拿5.5来举例:

将5.5转换为二进制后表示为101.1→1.0112^2→(-1)^01.011*2^2

此时S=0,M=1.011,E=2。计算机只需要把这三个值给存储起来便可以存储浮点数了。

那么关于S、M、E具体是如何存储的呢?以下是S、M、E的存储细则:

 

 

依照规定,E是无符号整型,而在存-1时在32位上是126,在64位上存的是1023,但是当E为负数时,又该如何表示?

例如0.5,转换为二进制后变为1.0*2^-1

为了阻止这种情况出现,IEEE754规定E的真实值必须加上一个中间数,对于8位的E,这个中间数就是127;对于11位的E来说,中间数为1023

所以1.0*2^-1中的E应该由-1→126,作为126存储在内存空间中

指数E取出的三种情况:

E不全为1或不全为0

这时浮点数就采用将E减去127或1023后得到真实值,再将M的前面加上第一位1。

E为全0

由于E-127后得到的真实值是-127,则指数为-127,此时此时有效数字M不再加上第一位的1,而是还原为0.xxxxx的小数。这样做是为了表示+-0,以及接近于0的很小的数字

E全为1

则真实值非常大,此时表示为+-∞

学了上述浮点数存储知识后,自己来试一下对上述程序输出结果预测是否正确吧~

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值