数据在内存中的存储——大小端,单/双精度,signed/unsigned?

一、整型在内存中如何存储

整型数据的二进制有三种,原码,反码,补码;详情请看https://blog.csdn.net/Tingjct/article/details/149308873?fromshare=blogdetail&sharetype=blogdetail&sharerId=149308873&sharerefer=PC&sharesource=Tingjct&sharefrom=from_link

而在内存中以补码形式存放;

  • 正数的原反补相同
  • 负数的原反补各不相同
  •  正数无符号位,负数的最高位是符号位,1代表负数,0代表负数

内存中存放补码的优势:

  • 可以使符号位和数值域统一
  • 计算加减时也可以统一处理(cpu只有加法器)
  • 补码和原码互相转换(取反加1)其运算过程相同,不需要额外的硬件电路

二、大小端字节序及其判断

了解整数在内存中的存储方式,现在来调试一个细节:

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

可以看到在内存中a的数值并没有以11223344来放,而是倒过来存储,这是因为什么?

2.1什么是大小端

其实超过一个字节的数据在内存中存储时,就会有有存储顺序的问题,按照不同的存储顺序,分为大端字节序存储和小端字节序存储。

  • 小端字节存储:把一个数据的低位字节内容存储到低地址处,高位字节内容存储到高地址处
  • 大端字节存储:把一个数据的低位字节内容存储到高地址处,高位字节内容存储到低地址处

2.2如何设计程序证明是大端存储还是小端存储

假如证明整型1的存储方式,用十六进制表示是0x00000001,若是小端存储(低字节序放在低地址处)则是10 00 00 00,若是大端存储(低字节序放在高地址处)00 00 00 10;也就是说只要取出来1在内存中的第一个字节,判断存储的是否为一,即可判定大小端存储方式;

代码如下:

#include <stdio.h>
int main()
{
	int i = 1;
	char ret = *(char*)&i;//强制转化为char*,解引用时一次才访问一个字节
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
}

将其包装为函数:

include<stdio.h>


int judge_sys()
{
	int i = 1;
	char ret = *(char*)&i;
	return ret;
}


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

2.3例题

在了解完大小端后,来小试牛刀一道经典例题:

int main()
{
    int a[4] = { 1,2,3,4 };
    int* ptr1 = (int*)(&a + 1);
    int* ptr2 = (int*)((int)a + 1);
    printf("%x,%x", ptr1[-1], *ptr2);
    return 0;
}

结果是什么?

首先先看a数组具体储存方式:

&a+1和(int)a+1又有什么区别呢?

所以&a+1中的加一是加上了一整个数组16个字节,而(int)a+1中的加一是加上了一个字节;

加一到底加多少取决于加的对象是什么类型的;而解引用访问时,到底访问了什么内容取决于大小端存储方式和数据类型;所以知道内存的存储方式是非常重要的!

2.4 无符号数&有符号数

既然说到了数据类型也很重要,那么除了横向知道数据类型有char,int,double等等;纵向的signed char&unsigned char、signed int&unsigned int等又是怎么储存的?在不同的数据类型进行转换时,底层逻辑又是什么呢?

首先以一道例题来引入

#include <stdio.h>

int main()
{
    char a = -1;
    signed char b = -1;
    unsigned char c = -1;
    printf("a=%d,b=%d,c=%d\n", a, b, c);
    return 0;
}//在VS的编译器上默认char就是signed char;

我们知道-1写成二进制是

1000 0000 0000 0000 0000 0000 0000 0001(源码)

1111 1111 1111 1111 1111 1111 1111 1110 (反码)

1111 1111 1111 1111 1111 1111 1111 1111(补码)

由于-1存进了char型数据中,所以char为1个字节8个bit位,即a和b中存的都是是1111 1111;

用%d打印出来,需要进行整型提升,由于a,b都是有符号的,所以进行提升的时候,最高位补上符号位上的1;打印结果就是-1;

再来看c,c是unsigned char,存的也是8个bit位 1111 1111;用%d打印出来,同样需要进行整型提升,但是正是由于c是无符号数,它没有符号位,提升的时候直接补0,结果是

0000 0000 0000 0000 1111 1111

提升完之后,%d认为这是一个正数,而正数的原反补相同,直接打印该数的十进制8个1为255

总之一句话:signed char有符号位,负数有原反补之分;unsigned char无符号位,全是正数;

最后2张图说明signed char和unsigned char的范围关系:

解释一下为什么1000 0000是-128

首先进行有符号数的整型提升:

1111 1111 1111 1111 1111 1111 1000 0000

依次写出反码,补码:

1000 0000 0000 0000 0000 0000 0111 1111

1000 0000 0000 0000 0000 0000 1000 0000(结果是-128)

总结:signed char 的取值范围为(-128~127)

unsigned char的取值范围(0~255)

2.5例题

了解了signed char&unsigned char的取值范围后,针对其练习几道题巩固一下

#include <stdio.h>

int main()
{
    unsigned char i = 0;
    for (i = 0; i <= 255; i++)
    {
        printf("hello world\n");
    }
    return 0;
}

结果打印多少个“hello world”?

分析:由上面的循环图可知,unsigned char的范围在0~255,当i加到255时,此时i在内存中时1111 1111,i+1就变成了0000 0000,即0,然后在进行一次又一次的大循环,i永远小于等于255;该程序的结果就是一个死循环;

三、浮点数在内存中的存储

3.1引入

浮点数和整型在内存中的储存方式相同吗?我们以一道例题引入:

int main()
{
    int n = 9;
    float* pfloat = (float*)&n;
    printf("n=%d\n", n);
    printf("*pfloat=%f\n", *pfloat);

    *pfloat =9.0;
    printf("num=%d\n", n);
    printf("*pfloat=%f\n", *pfloat);
    return 0;
}

打印结果是什么?

你可能猜想依次是9,9.000000,9 ,9.000000;但结果是否是这样呢?答案是

可见我们猜想的只对了第一个和第四个,错了一半;由此说明由整数转为浮点数和由浮点数转为整数天差地别,说明浮点数和整数在内存中存储方式完全不同;

3.2浮点数存储方式解读

根据国际标准IEEE754,任意一个二进制浮点数V可以表示成下面的形式:

V=(-1)^{s}*M*2^{E}

  • (-1)^{s}代表符号位,当S=0时,V为正数;当S=1时,V为负数;
  • M代表有效数字,1<=M<2;
  • 2^{E}表示指数位

如浮点数5.5,以小数点为界,小数点之前的写成二进制为0101,小数点之后按照2^(-1),2^(-2)等这样的权重,小数点之后写成100 000,在整体合起来为101.100000,转化成有效数字是1.01*2^(2),

M=1.01,E=2,S=0;

float型浮点数中8bit的E:求出E+127(中间数),在转化位8位的二进制数

double型浮点数中11bit的E:求出E+1023(中间数),在转化位11位的二进制数

M存储时一般省略小数点前的1,直接存小数点后面的数值,不够的位后面补0

例如5.5的M=1.01,E=2,S=0;他的float型浮点数如何写,首先第一位是0,E+127=129,8位的二进制数位 1000 0001;M省略1.01前的1,存成0100 0000 0000 0000 0000 000;

即float n=5.5,在内存中为0 1000 0001 0100 0000 0000 0000 0000 000

注意:由于浮点数权数固定,位数固定,所以浮点数可能不会精准保存数据。当然位数越多越精准,这就说明了double(双精度浮点数)比float(单精度浮点数)精准的原因

3.3例题回顾

再将引入例题做一遍:

int main()
{
    int n = 9;
    float* pfloat = (float*)&n;
    printf("n=%d\n", n);
    printf("*pfloat=%f\n", *pfloat);



    *pfloat =9.0;
    printf("num=%d\n", n);
    printf("*pfloat=%f\n", *pfloat);
    return 0;
}

详细分析如下图:

四、小结

了解完整数和浮点数在内存中的存储方式,相信你对数据类型的逻辑已经非常透彻了;希望这篇文章对你有莫大的帮助,本章知识难度稍微高点,慢慢理解尝试自己画图解决,相信你会有收获的!共勉!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值