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

目录

一、整数在内存中的存储

二、大小端字节序

(1)什么是大小端字节序

(2)用程序判断当前机器的字节序

(3)练习题

① 练习1

② 练习2

③ 练习3

④ 练习4

⑤ 练习5

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

(1)国际标准 IEEE 754

(2)从内存中读取浮点数

(3)对开头题目的解析

(4)浮点数的比较


一、整数在内存中的存储

        整数的二进制有原码、反码、补码三种形式,在内存中是以补码的形式存储的。详细讲解请看 http://t.csdnimg.cn/qqGDz 。

二、大小端字节序

(1)什么是大小端字节序

        内存中以一个字节为单元,超过一个字节的数据(short、int、long)在内存中存储,必然就有存储顺序的问题。另外,对于大于8位的处理器,例如16位、32位处理器,由于寄存器宽度大于一个字节,也会存在多个字节的存储顺序的问题。

        在内存中数据是以二进制存储的,这里为了方便书写和观察,用十六进制表示。比如十六进制 0x11223344,每两个十六进制数,就代表一个字节,可以用以下的顺序在内存中存储:

11 22 33 44、11 33 22 44、11 22 44 33、44 33 22 11……,只要是以一个字节为整体,什么顺序都可以,存进去了能复原就行。但是对于复原,明显是正序的 11 22 33 44 和逆序的 44 33 22 11 从内存中取出来时,更容易复原。因此,就采用了这两种顺序,并取了名字:

  • 大端(存储)模式:数据的低位字节的内容存放在内存的高地址处;高位字节的内容存放在内存的低地址处。(正序存放)
  • 小端(存储)模式:数据的低位字节的内容存放在内存的低地址处;高位字节的内容存放在内存的高地址处。(逆序存放)

        我们常用的X86结构(比如Intel处理器)采用的小端模式;KEIL C51(单片机、嵌入式)采用的大端模式;大部分ARM、DSP 采用小端模式;有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

(2)用程序判断当前机器的字节序

        分析:对于数字 0x0001,在小端模式中是以 01 00 00 00 的顺序存储的(最低地址单元存放的是 01);大端模式中是以 00 00 00 01 的顺序存储的(最低地址单元存放的是 00)。因此,我们只需要取出最低地址单元的内容,就能判断是小端还是大端模式。

        实际上对于一个 int 类型的变量,对其取地址,这个地址是最低字节单元的地址。然后要把取得的地址强制转换成 char*,这样在解引用的时候,才会得到一个字节的内容,而不是四个字节的内容。

(3)练习题

        数据类型的作用:

  • 申请内存空间。(比如:char是1字节、short是2字节、int是4字节。)
  • 告诉如何看待内存中存放的数据。(比如:int 和 float 都是4个字节,如何区分内存中存放的的二进制数,就看它们的数据类型。)

① 练习1

int main()
{
	char a = -1;
    // -1 默认为 int 型
    // 10000000 00000000 00000000 00000001 -- 原码
    // 11111111 11111111 11111111 11111110 -- 反码
    // 11111111 11111111 11111111 11111111 -- 补码
    // char 只能装 1个字节,截断,保留低位
    // 11111111 -- 补码
    // VS中默认char就是signed char
	signed char b = -1;
	unsigned char c = -1;
    // 占位符是%d,用有符号整型看待内存中的数
    // 首先整型提升
    // 11111111 11111111 11111111 11111111 -- 有符号数,高位补符号位 -- 补码
    // 00000000 00000000 00000000 11111111 -- 无符号数,高位补0 -- 补码
    // 然后把他们看作有符号数
    // 11111111 11111111 11111111 11111111 -- 真值为-1
    // 00000000 00000000 00000000 11111111 -- 真值为255
	printf("a=%d,b=%d,c=%d", a, b, c);
	return 0;
}

② 练习2

int main()
{
	char a = -128;
    // -128 默认为 int 型
    // 10000000 00000000 00000000 10000000 -- 原码
    // 11111111 11111111 11111111 01111111 -- 反码
    // 11111111 11111111 11111111 10000000 -- 补码
    // 10000000 -- 补码 -- 截断
    // 占位符%u,无符号整型,整型提升
    // 整型提升
    // 11111111 11111111 11111111 10000000 -- 有符号数,高位补符号位
    // 看作无符号数打印
    // 真值为 4294967168
	printf("%u\n", a);
	return 0;
}

int main()
{
	char a = 128;
    // 00000000 00000000 00000000 10000000 -- 原、反、补码
    // 10000000 -- 截断
    // %u,无符号整型
    // 整型提升
    // 11111111 11111111 11111111 10000000 -- 有符号数,高位补符号位
    // 看作无符号数
    // 4294967168
	printf("%u\n", a);
	return 0;
}

③ 练习3

int main() {
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i; // -1,-2,-3,-4……-1000,但char的取值范围没这么大
	}
	printf("%d", strlen(a));
	return 0;
}

        singed char 在内存中的取值范围:-128~127

        unsined char 在内存中的取值范围:

        char在VS中默认为 signed char ,因此每次循环内的 -1 - i 的取值是:-1,-2,……,-128,127,126,……,2,1,0(停止符)。字符串长度是:128 + 127 = 255。

④ 练习4

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

        因为 i 为 unsigned char 类型,所以 i 的取值范围是 0~255,始终满足循环的判断条件,因此会陷入死循环。

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}

        因为 i 为 unsigned int 类型,取值始终大于等于0,因此也会陷入死循环。

⑤ 练习5

//X86环境 ⼩端字节序
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是数组的地址,加1就到了数组结尾的地址,再赋值给ptr1强制转换为 int*,就是整型元素的地址,ptr1-1指向4。

        a是数组首元素的地址,强制转换为int,加1就相当于把a这个地址值+1,实际上就是加了一个字节单元。数组a的十六进制小端字节序是 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00,那么 (int)a+1 就会指向01后面的一个字节,再强制转换为 int*,向后读取4个字节的数据为 00 00 00 02,书写的十六进制是 02 00 00 00,这就是ptr2指向的内容。

        对于%p,输出的是十六进制地址,不会有0x,但是会保留地址前面的0;对于%x,不会有0x,也不会保留十六进制前面的0;想输出有x0的十六进制,占位符得是%#x。

        因此,对于上面的代码,运行结果应该是 4,2000000。

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

浮点数形式:3.14159、1E10(科学计数法)等。

浮点数类型:float、double、long double。

浮点数范围:最小值、最大值、精度,见头文件 float.h。

        看以下代码,变量 *pFloat 、n 分别以浮点数、整数的形式存取数据,会发现读取出的数值不同,这是因为它们在内存中的存储方式不同:

浮点数在内存中的表示:使用国际标准IEEE(电气和电子工程协会)754

(1)国际标准 IEEE 754

① 转换形式

二进制浮点数 V 转换为  V~=~(-1)^S*M*2^E 的形式。

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

举例:十进制 -5.5, 写成二进制 -101.1, 相当于 - 1.011\times2^{2},改为 V 的格式 ,那么 S = 1,M = 1.011,E = 2。

② 浮点数在内存中的空间分配

float:共32位,符号位S(1位)+ 指数E(8位),有效数字M(32位);

double:共64位,符号位S(1位)+ 指数E(11位),有效数字M(52位)。

③ 有效数字M的特别规定

        因为 1\leq M<2,所以 M 可以写成 1.xxxxx 的形式,1 是固定有的,为了保存更多位的有效数字,会省去整数部分的 1,只把后面的小数位保存到内存。从内存读的时候,再把整数 1 加上。例如,M = 1.011,只存储011。

④ 指数E的特别规定

        E是一个无符号整数,那么如果 E 为 8 位取值范围是 0~255;E 为 11 位取值范围是 0~2047。但是 E 在科学计数法中存在负数,为了表示负数,E 会加一个中间值让负的变成正的。如果 E 为 8 位,中间值是 127;E 为 11 位,中间值是 1023。读取的时候,E 加一个中间值即可。例如,8位 E=10,那么存储的值为 10 + 127 = 137,即 10001001。

(2)从内存中读取浮点数

① E不全为0或不全为1

        按正常的来,读取到的 E 减去中间值,M 补上整数部分的1。

② E全为0

        这时浮点数的真实值为 ± 0 或者很接近 0 的数。E 等于 1-127,M 不再补1,而是 0.xxxx。

③ E全为1

        若 M 全0,浮点数真实值表示 ± 无穷;若 M 非全 0,浮点数真实值表示 NaN(非数值,如 0 除以、无穷 - 无穷)。

(3)对开头题目的解析

ps:在内存中整型数据以补码形式存储。

(4)浮点数的比较

        二进制小数表示的十进制:

        浮点数在计算机中难以精确表示,比如十进制 3.14 = 3 + 0.125 + …… = 11.001……,会发现永远凑不够,并且浮点数的 M 位的存储空间也有限。因此,浮点数的比较不能直接用 == (会让比较结果不准确),如下所示(0.1 和 0.2 的二进制小数位是无限循环的):

        更准确的判断方法:将两者做差的绝对值,若绝对值小于一个可容忍的误差值,则视为两者相等,如下图所示:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值