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

对二进制的数字表示 - 原码反码补码:

在C语言当中, 计算机对二进制的数字表示会用到原码, 反码以及补码.

对于有符号的整型来说(int), 32位的第1位是符号位.

原码:将最高位作为符号位(0表示正,1表示负),其它数字位代表数值本身的绝对值的数字表示方式。

反码:正数的反码和原码相同, 负数的话除了最高位的符号位不变, 其他数字位按位取反(1变0, 0变1).

补码:正数的补码和原码相同, 负数的话就是反码+1(从最低位那+1).

例如:

int  a = -15;

1000 0000 0000 0000 0000 0000 0000 1111 - 原码

1111 1111 1111 1111 1111 1111 1111 0000 - 反码

1111 1111 1111 1111 1111 1111 1111 0001 - 补码

注意: 正数三码合一(原码反码补码都相同)!!!

 原码反码补码的意义:

那么为什么科学家要发明原码反码补码呢?不能全都用原码吗? 原因很简单, 因为计算机没有减法器只有加法器.

0000 0000 0000 0000 0000 0000 0000 0001 为 1的原码

1000 0000 0000 0000 0000 0000 0000 0001 为-1的原码

如果原码相加就变成:

1000 0000 0000 0000 0000 0000 0000 0010 为-2的原码, 明显不符.

所以聪明的计算机科学家发明了补码来用于计算机的计算(计算机存储的是补码, 原码是自己构造的要转换成补码才是计算机用的).

 字节序 - 以字节为单位来讨论存储顺序:

字节序是以字节为单位来讨论存储顺序的, 通常有两种存储规则:大端字节序存储和小端字节序存储:

小端字节序存储:

把一个数据的低位字节的内容存放在低地址处, 高位字节的内容存放在高地址处.

大端字节序存储:

把一个数据的低位字节的内容存放在高地址处, 高位字节的内容存放在低地址处.

对于数据来说, 如0x 11 22 33 44中44是低位(小的一端).

一般来说机器是小端字节的多一些.

下面写一个判断机器是小端存储还是大端存储的代码:

#include <stdio.h>

int check() {
    int a = 1; // 内存中为0000....0001
    // a的地址从低位开始取,用char*指针类型取第一个字节.
    // 访问第一个字节判断是0000还是0001.
    // 是0000证明高位字节放在了低地址为大端.
    // 是0001则是低位字节放在了低地址为小端.
    return *((char*)&a); // 强制类型转换再解引用.
}

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

signed和unsigned(有符号和无符号) 

C语言中整型都有分signed和unsigned, 默认short和int和long和longlong都为signed.

C语言没有明确规定char是signed还是unsigned, vs默认是signed(一般都是).

浮点数在内存中的存储:

浮点数的存储规则: (-1) ^ S * M * 2 ^ E.

其中(-1) ^ S表示符号位, 当S0代表是正数, 当S1则为负数.

M代表有效数字, 大于等于1小于2. 例如:1.1, 1.123, 1.666, 1.0等.

2 ^ E代表指数位.

例如:

十进制的5.0, 写成二进制是101.0, 相当于1.01 * 2 ^ 2.

其中S = 0, M = 1.01, E = 2.

十进制的5.5是二进制是101.1, 101是5, 后面的1是2的-1次方分之一就是0.5.

在小数点后的数字每往后就是2的负多少次方, 比如0.11的二进制写成十进制是0.75. 0.101的二进制写成十进制是0.5 * 1 + 0.25 * 0 + 0.125 * 1 = 0.625.

对于float的存储方式(32个bit位), 第1位是符号位S, 后8位是指数位E, 剩下23位是有效数字位M.

对于double的存储方式(64个bit位), 第1位是符号位S, 后11位是指数位E, 剩下52位是有效数字位M.

因为double的有效数字位大于float的, 所以说double的精度比float高.

对于M来说:

    因为1 <= M < 2, M可以写成1.xxxxxxxxxx的形式, 所以默认第一位就是1, 故舍去, 只保存xxxxx的部分.

    取出来时自动补上1, 这样可以多存一位有效数字.

对于E来说:

    E是个无符号整数(unsigned int).

    如果E是8位则取值范围为0 ~ 255, 为11位则为0 ~ 2047.

    但是科学计数法中E是可以为负数的, 所以存入内存是E的真实值要加上一个中间数, 8位和11位中间数分别是127和1023.

    比如2 ^ 10的E是10, 所以保存成32位浮点数时, 必须保存成10 + 127 = 137, 即10001001(二进制).

当E从内存中取出时分三种情况:

    ①E的二进制不为全0或全1, 则E取出来的值换算成十进制减去127或1023, 得到真实值然后再讲有效数字M前加上第一位的1.

    比如0.5的二进制形式为0.1, 由于规定整数部分必须为1, 所以为1.0 * 2 ^ -1,

其阶码为-1 + 127 = 126, E表示为01111110, 尾数1.0去掉整数部分1, 尾部补0到23个0, 则这个浮点数的二级制表示为0 01111110 000...000(23个0).

    ②当E为全0时,浮点数E等于1 - 127(或1 - 1023), 有效数字M第一位不加1了,改成0, 即还原成0.xxxxx的小数, 这样做为了表示正负0以及接近于0的很小的数字.

    ③当E为全1, 这是如果有效数字M权威0, 表示正负无穷大(符号取决于s).

 下面是代码演示浮点数存储在内存中的原理:

#include <stdio.h>

int main() {
    int a = 3;
    float* p = (float*)&a; // 强制转换成float类型的指针.
    printf("%d\n", a);
    printf("%f\n", *p);
    *p = 9; // 改变指针解引用的值, 因为p是float型的指针所以按照float的存储规则改变.
    printf("%d\n", a);
    printf("%f\n", *p);
    return 0;
}

运行截图:

 a写成二进制是0000 0000 0000 0000 0000 0000 0000 0011.

当以float形式的指针接受a的地址时, 将会按照浮点数的规则计算数值.

所以对于a来说符号位0, 所以是正数, 符号位后8位指数位E全为0, 有效数字又非常小只打印6位小数所以是0.

对于*p更改成9(二进制是1001, (-1) ^ 0 * 1.001 * 2 ^ 3)按照浮点数规则更改则为:

0 10000010 00100000000000000000000, 将其转换成十进制就是1091567616.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值