C语言初阶——数据在内存中的存储详解

本文详细介绍了C语言中整数(包括正负整数的原码、反码和补码表示)和浮点数(遵循IEEE754标准)在内存中的存储方式,讨论了大端字节序和小端字节序的区别,以及如何通过编程判断机器字节序。
摘要由CSDN通过智能技术生成

C语言初阶——数据在内存中的存储详解

  1. 整数在内存中的存储

正整数的原、反、补码都相同,负整数的三种表示方法各不相同。

原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码+1就得到补码。

反码得到原码也是可以使用:取反,+1的操作。(符号位不变)

对于整数来说,内存中储存的是补码

  1. 大端字节序存储与小端字节序存储
#include<stdio.h>
int main()
{
	int a = 0x11223344;
	return 0;
}

调试结果:0x0035F90C 44 33 22 11

我们在调试的过程中,发现数字 a 是以字节序倒着存储的,这样的存储方式我们称为小端字节序存储

大端字节序存储:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处
例如:0x11 22 33 44

小端字节序存储:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处
例如:0x44 33 22 11

口诀:(数字的)大端在前为大端,小端在前为小端。

  • 设计一个程序,判断当前机器的字节序:
void fun()
{
	int a = 1;//该数字存储方式可能为00 00 00 01或01 00 00 00
	char* p = (char*)&a;//通过指针获取该数字最左边的两个十六进制位的地址,解引用后即可判断是0/1
	if (*p == 1) {
		printf("小端字节序存储\n");
	}
	else {
		printf("大端字节序存储\n");
	}
}
int main()
{
	fun();
	return 0;
}
  1. 关于整数存储的经典问题
  • 练习一:

注意:在VS2022环境中,char类型均为有符号类型

#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;
}

输出结果:a = -1 b = -1 c = 255

分析过程:

#include <stdio.h>
int main()
{
	char a = -1;
	//先得到-1的补码:11111111111111111111111111111111
	//由于a为char类型,会发生截断,即char类型中储存的内容为:11111111
	//以%d形式打印,会出现以char类型的整型提升,得到11111111111111111111111111111111
	//转化为原码为10000000000000000000000000000001,即输出结果为-1
	signed char b = -1;
	//由于在VS2022中,char类型就为signed char类型,所以输出结果相同
	unsigned char c = -1;
	//同理,储存的内容为11111111,以%d形式打印,会出现以unsigned char类型的整型提升
	//得到的结果为00000000000000000000000011111111,由于是无符号类型,该值补码等于原码
	//即输出结果为255
	printf("a = %d b = %d c = %d", a, b, c);
	return 0;
}

注意:在发生整型提升时,是按照赋值时的类型提升,而不是按照打印时的占位符。

  • 练习二:
#include<stdio.h>
int main()
{
	char a = -128;
	char b = 128;
	printf("a=%u b=%u", a, b);
	return 0;
}

输出结果:a=4294967168 b=4294967168

分析过程:

#include<stdio.h>
int main()
{
	char a = -128;
	//a的补码为11111111111111111111111110000000
	//char类型发生截断后,存储的是10000000,以char类型发生整型提升后
	//可以得到11111111111111111111111110000000
	//由于是无符号数,该补码就等于原码,输出结果为4294967168
	char b = 128;
	//b的补码为00000000000000000000000010000000
	char类型发生截断后,存储的是10000000,以char类型发生整型提升后
	///可以得到11111111111111111111111110000000,所以两数输出结果相同
	printf("a=%u b=%u", a, b);
	return 0;
}
  • 练习三:
#include<stdio.h>
#include<string.h>
int main()
{
	char str[1000] = { 0 };
	for (int i = 0; i < 1000; i++) {
		str[i] = -(1 + i);
	}
	printf("%zd", strlen(str));
	return 0;
}

输出结果:255

题目解析:

#include<stdio.h>
#include<string.h>
int main()
{
	char str[1000] = { 0 };
	for (int i = 0; i < 1000; i++) {
		str[i] = -(1 + i);
	}
	//字符数组str会被不断地从-1开始不断减少(括号中的为补码)
	//-1(11111111) -2(11111110) ... -127(10000001) -128(10000000)
	//如果再将其-1,其中-129(原码:100...0010000001)会产生截断得到127(01111111)
	//再依次减少,最终得到0(00000000),然后-1(11111111) -2(11111110)...产生循环
	//由于'\0'的ASCII码值为0,strlen()函数是统计字符'\0'之前的字符个数,即得到结果128+127=255
	printf("%zd", strlen(str));
	return 0;
}
  • 练习四:
#include<stdio.h>
typedef unsigned char uchar;
typedef unsigned int uint;
int main()
{
	for (uchar i = 0; i <= 255; i++) {
		printf("这是第%u次循环\n", i);
	}
	for (uint j = 9; j >= 0; j--) {
		printf("这是第%u次循环\n", j);
	}
	return 0;
}

注意:上述两个for-loop均会进入死循环,此代码并无法真正执行。

分析过程:

#include<stdio.h>
typedef unsigned char uchar;
typedef unsigned int uint;
int main()
{
	for (uchar i = 0; i <= 255; i++) {
		printf("这是第%u次循环\n", i);
	}
	//在i达到255之前,循环正常进行,当i达到255(11111111)时,再+1得到256(100000000)
	//发生截断,以00000000存入uchar中,i的值变为0,继续循环
	for (uint j = 9; j >= 0; j--) {
		printf("这是第%u次循环\n", j);
	}
	//在j达到0前,循环正常进行,当j等于0(0000...00000)时,再-1得到-1(补码:111111....1111111)
	//由于该数字是uint类型,该数直接以111111....1111111打印,得到结果4294967295
	return 0;
}
  • 练习五:
#include <stdio.h>
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;
}

输出结果:0x4 0x2000000

#include <stdio.h>
//在x86与小端模式的环境下
int main()
{
	int arr[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&arr + 1);//由于&arr的类型为int(*)[4]类型,需要强制转换为int*类型
	int* ptr2 = (int*)((int)arr + 1);//将数组的地址强制转化为了int类型,操作后又转化为了int*类型
	printf("%#x %#x", ptr1[-1], *ptr2);
	//数组名为首元素的地址(假设为0x0012FF40),转化为int类型后+1得到0x0012FF41
	//强制转化为int*类型后,该指针解引用后从0x0012FF41开始向后访问四个字节
	//由于是小端模式存储,该数组在内存中按照地址存储的内容是:
	//01 00 00 00 02 00 00 00 03 00 00 00 ......
	//所以该指针解引用后访问的是00 00 00 02
	//即得到十六进制数0x02000000
	return 0;
}

  1. 浮点数在内存中的存储
  • 根据国际标准IEEE754规定,任意⼀个二进制浮点数 V 可以表示成下面的形式:

V = ( − 1 ) s ∗ M ∗ 2 ∗ E V=(-1)^s*M*2*E V=(1)sM2E

​ S 表示符号位,当 S=0 时,V为正数;当 S=1 时,V为负数。

​ M 表示有效数字,其中1<=M<2,E 表示指数位

例如:十进制的5.5,写成二进制是 101.5 ,相当于 1.015×2^2 。我们可以得出S=0,M=1.015,E=2。

十进制的-5.0,写成二进制是-101.0 ,相当于-1.01×2^2 。那么,S=1,M=1.01,E=2。

  • 根据标准规定:

对于32位的浮点数(float类型),最高的1位存储符号位 S,之后的的8位存储指数 E,剩下的23位存储有效数字M

对于64位的浮点数(double类型),最高的1位存储符号位 S,之后的11位存储指数E,剩下的52位存储有效数字M

  • 浮点数存储的过程

M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分,规定在存储 M 时,将前面的1舍去,只保留小数部分。例如1.011在 M 中只保存011,读取时再将前面的1加上,这样就可以多存储一位的有效数字。

对与指数 E 来说,首先 E 是一个无符号数(unsigned int类型),因此存入内存时 E 的真实值必须再加上一个中间数,对于8位的 E(取值范围是0~255),这个中间数是127,对于11位的 E(取值范围是0~2047),这个中间数是1023。例如,浮点数1.01*2^10的 E 是 10,所以保存为float类型时,必须保存成10+127=137,即10001001。

  • 浮点数读取的过程

    E 不全为0或不全为1:

    这时,浮点数就采用下面的规则表示,即指数 E 的计算值减去127(或1023),得到真实值,再将有效数字 M 前加上第一位的1。

    例如二进制数字0.1,根据规定将小数点右移1位,得到1.0*2^(-1),其中 E 等于-1+127(中间值)=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位 00000000000000000000000,则其二进制表示形式为:

    0 01111110 00000000000000000000000
    

    E 全为0:

    这时,浮点数的指数E等于-126(或者-1022)即为真实值,有效数字M不再加上第⼀位的1,直接还原为0.xxxxxx的小数,这样做是为了表示±0,以及接近于0的很小的数字。(由于不表示1了,其指数为自动+1)

    E 全为1:

    这时,如果有效数字M全为0,通常用于表示无穷大。

  • 练习:

#include<stdio.h>
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;
}
  • 题目解析:
#include<stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;//将&n强制类型转化为float*类型读取
	printf("n的值为:%d\n",n);//以%d读取
	printf("*pFloat的值为:%f\n",*pFloat);//以%f读取
	*pFloat = 9.0;//解引用后,按照存入浮点数9.0
	printf("num的值为:%d\n",n);//以%d读取
	printf("*pFloat的值为:%f\n",*pFloat);//以%f读取
	return 0;
}
//题目解析:
//首先,我们写出9的补码:00000000000000000000000000001001
//按照%d读取,可以得到9,按照%f打印,0 00000000 00000000000000000001001
//其中S=0,E为全零,E的真实值为-126,M的真实值为0.00000000000000000001001
//即V=(-1)^0*0.00000000000000000001001*2^(-126),为一个极小的数字,获得结果为0.000000
//当以float类型赋值为9.0时,存储的内容为0 10000010 00100000000000000000000
//以%d打印时,由于符号位为0,直接获得结果为1091567616,以%f打印就为9.000000

输出结果:

n的值为:9
*pFloat的值为:0.000000
num的值为:1091567616
*pFloat的值为:9.000000

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值