C语言的数据存储详解

C语言数据存储

类型的基本归类

有无符号的意义:生活中有写数据是没有符号之分的,将最高位(即符号位)利用可以扩大范围

整形包含类型占用空间备注
charunsigned char1Byte字符的本质是ASCII值,是整形
signed char1Byte不同于直接创建 int a时,a为signed
char1Bytechar 到底是signed 还是 unsigned 标准是未定义的,取决于编译器(VS下为signed)
shortunsigned short (int)2Bytes
(signed) short (int)2Bytes
intunsigned int4Bytes
(signed) int4Bytes
longunsigned long (int)4/8BytesC语言在规定类型大小时,只规定了 sizeof(long) >= sizeof(int)
(signed) long (int)4/8Byteslong 在x86为4,x64为8
long longunsigned long long (int)8Byteslong long 为C99中引入
(signed) long long (int)8Bytes
浮点型占用空间备注
float4Bytesfloat 的精度低,存储范围小,double 精度高,范围大
double8Bytes
long double不定长度可能会应编译器和平台的不同而产生差异,可能和 double 一样长,可能更长(8/12/16Bytes)

long double 在 C99 标准中被引入,用以表示更高精度的浮点数

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

以及不同类型所对应的指针类型、空类型(void)

空类型常用于函数的返回类型(表示无返回)、函数的参数(无需参数)等

类型的意义

  • 不同的类型对应所开辟的内存空间大小不同(内存大小决定使用范围)
  • 程序看待对应内存空间的视角(程序以何种规则读取)

以下程序用于验证整形和浮点型的存取规则不同

#include <stdio.h>

int main()
{
    int n = 9;//开辟4bytes
    float* pFloat = (float*)&n;//把n的地址放到pfloat里面,实际还是指向那整形的4bytes

    printf("n的值为:%d\n", n);//9
    printf("*pFloat的值为:%f\n", *pFloat);//0.000000
    //这里按照浮点的规则取出数据,结果错误,说明整形和浮点的存储方式不同

    *pFloat = 9.0;
    printf("num的值为:%d\n", n);//1,091,567,616
    printf("*pFloat的值为:%f\n", *pFloat);//9.000000
    return 0;
}

数据在内存中的存储

整形在内存中的存储

整形表示的范围在limits.h中定义(如果不用到上限和下限的话不用引)

在这里插入图片描述

在这里插入图片描述

程序中整形的二进制表示方式有三种,分别为原码、反码和补码

  • 原码即为整数的二进制表示,对于有符号类型,最高位为符号位,0表示正数,1表示负数
  • 正数的三码相同
  • 负数的反码为原码按位取反(最高位不变),补码为反码加1

以7和-7为例(int)

整形表示方式数据
7原码00000000 00000000 00000000 00000111
反码00000000 00000000 00000000 00000111
补码00000000 00000000 00000000 00000111
-7原码10000000 00000000 00000000 00000111
反码11111111 11111111 11111111 11111000
补码11111111 11111111 11111111 11111001

在计算机系统中,数值一律用补码来表示和存储。通过补码,可以将负数视为正数,将加法和减法统一处理(CPU只有加法器)此外,补码与原码相互转换(补码的补码即为原码),其运算过程是相同的,不需要额外的硬件电路

int main()
{
    int a = 20;
    //原、反、补:00000000 00000000 00000000 00010100
    //    十六进制      00       00       00       14  (0x 00 00 00 14)

    int b = -10;
    //最高位为符号位
    //原:10000000 00000000 00000000 00001010
    // 0x 80       00        00         0a
    //反:11111111 11111111 11111111 11110101
    // 0x ff        ff        ff         f5
    //补:11111111 11111111 11111111 11110110
    // 0x ff        ff        ff         f6
    //对补码取反:
    //      10000000 00000000 00000000 00001001
    // +1:
    //      10000000 00000000 00000000 00001010 得到原码

    //20-10 = 20 + (-10)
    int c = a + b;
    //00000000 00000000 00000000 00010100
    //11111111 11111111 11111111 11110110
    //相加得到一个补码:
    //(1)00000000 00000000 00000000 00001010 最高位被丢掉
    //0x 00 00 00 0a  (10)
    return 0;
}
大小端

大小端即计算机系统中两种不同的数据存储方式

大端存储模式 将数据的低位保存在内存中的高位地址,将数据的高位保存在低位地址

小端存储模式 将数据的低位保存在内存中的低位地址,将数据的高位保存在高位地址

由于数据长度、寄存器大小不同,且计算机的存储是以字节为单位的,这就导致了不同长短的数据和寄存器(如16位、32位)之间存在数据存放的顺序问题(即哪些数据放在高地址,哪些放在低地址),大小端存储模式就是为了解决这类问题

我们常用的X86(x64) 结构是小端模式

这里以上面这段简单的代码为例,以下分别位a,b在内存中的存储

在这里插入图片描述

在这里插入图片描述

实际上,数据存储的规则可以是多种的,只要保证存入和取出的方式相同即可

浮点型同样有大小端,但对一个字节的数据,没有顺序可谈

//判断当前环境是大端还是小端
#include <stdio.h>
int check()
{
    int i = 1;
    return (*(char*)&i);
    //将地址强制转为char*,即只读取最低位的数据
    //当为小端,得到1;当为大端,得到0
}
int main()
{
    int ret = check();
    if (ret == 1)
    {
        printf("小端\n");
    }
    else
    { 
        printf("大端\n");
    }
    return 0;
}
整形提升和截断

截断发生在数据存储时,整形提升发生在使用时

有关整形提升和截断的内容,详见另一篇博客

C语言对类型的转换-CSDN博客

#include <stdio.h>
int main()
{
    char a = -1;
    //char 在VS下是有符号的

    // -1 是整形,为32位,要存入char,要发生截断
    //原:10000000 00000000 00000000 00000001
    //反:11111111 11111111 11111111 11111110
    //补:11111111 11111111 11111111 11111111
    //发生截断
    // a -- 11111111
    // %d 打印有符号整形,发生整形提升,对于有符号数,高位补1
    // 11111111 11111111 11111111 11111111 -- 补码
    // 10000000 00000000 00000000 00000000 -- 取反
    // 10000000 00000000 00000000 00000001 -- +1 得到原码
    // b的打印同理
    // 截断和大小端无关,相当于是从内存中拿出数据后再发生截断

    signed char b = -1;

    unsigned char c = -1;
    //原:10000000 00000000 00000000 00000000
    //反:11111111 11111111 11111111 11111110
    //补:11111111 11111111 11111111 11111111
    // c -- 11111111
    //无符号数高位补0
    // 00000000 00000000 00000000 11111111 --- 直接就是原码

    printf("a=%d,b=%d,c=%d", a, b, c);
    //VS环境下打印  a=-1,b=-1,c=255
    return 0;
}

浮点型在内存中的存储

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

(-1)^S * M * 2^E

(-1)^s — 符号位,当s=0,V为正数;当s=1,V为负数

M — 有效数字,大于等于1,小于2

2^E — 指数位

6.0的二进制表示就是 110.0

s=0,M=1.1,E=2

即为1.1×2^2

6.5就是110.1

s=0, M=1.101,E=2

即为1.101×2^2

浮点型的存储规则

float - 4bytes - 32 bit

最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M

double - 8bytes - 64bit

最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M

根据十进制转换二进制的计算规则(小数部分×2取整,得0为止)可知,当一个数的小数部分在多次乘法取整后仍不为0时,可能会将有效数字所占长度全部使用,后面的数据将无法存储。即浮点数存在丢失精度的情况

IEEE 754规定,在计算机内部保存M时,默认的第一位总是1,因此可以被舍去,只保存后面的小数部分(即保留24/53位有效数字),由此可以提高精度

E为一个无符号整数(unsigned int),当8为8位时,其范围为0~255,而科学计数法中的指数可以存在负数,因此IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,即加上127/1023

E的不同情况
  • E不全为0或不全为1

    此时,E的真实值计算即为(E-中间值)

  • E全为0

    E+127 = 0,这时,浮点数的指数E等于1-127(或者1-1023)即为真实值

    有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于

    0的很小的数字

  • E全为1

    E + 127 = 255

    这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)即 1.xxxxxx * 2^128


运用

利用这些规则,分析上面的代码

#include <stdio.h>

int main()
{
    int n = 9;
    //00000000 00000000 00000000 00001001
    //0 00000000 00000000000000000001001
    //s=0 E=00000000

    float* pFloat = (float*)&n;

    printf("n的值为:%d\n", n);//9
    printf("*pFloat的值为:%f\n", *pFloat);//0.000000
    //0 00000000 0000000000 0000000001 001
    //s=0 E=00000000
    //一个接近0的很小的数


    *pFloat = 9.0;
    //9.0 --- 1001.0
    //s=0 E=3 --- 00000011
    // 3 + 127 = 130 --- 1000 0010
    //0 10000010 0010000000 0000000000 000
    printf("num的值为:%d\n", n);//1,091,567,616

    //01000001 00010000 00000000 00000000
    printf("*pFloat的值为:%f\n", *pFloat);//9.000000
    return 0;
}
  • 35
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值