C语言笔记13:数据在内存中的存储

文章目录

C语言笔记13:数据在内存中的存储

一、整数在内存中的存储

整数在内存中存放的是补码
补码:

正数原码、反码、补码一样

负数:

  • 原码是符号位为1

  • 反码是原码除了符号位按位取反(其实反码和原码是一个性质)

  • 补码是反码+1
    存放补码的原因:

  • 可以使用加法来进行减法运算,补码是模运算

  • 0的表示只有一个没有+0和-0

二、大小端字节序和字节序判断

什么是大小端

当一个数据比如short类型等,大小超过一个字节,就会有字节序的问题。

一个整数1存放在内存中:

大端字节序:

小端字节序:

练习

2.1判断当前机器大小端
c 复制代码
int Check_Sys()//或者也可以叫做Is_Small()
{
	int i = 1;
	return (*(char*)&i);
}

int main()
{
	int ret = Check_Sys();
	if(ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");//ret==0
	}
}
c 复制代码
int Check_Sys()
{
	union
	{
		int i;
		char c;
	}un;
	un.i = 1;
	return un.c;
}
2.2判断下面程序的运行结果
c 复制代码
#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;
}

字符型整数运算会发生整型提升。赋值的时候就是把寄存器中的4字节整型的-1最低位字节赋值给了三个变量,看这三个数据在内存中的情况:

printf("a=%d",a);这里会把a的值拿出来,放入寄存器中,由于a是一个char类型,在visual studio 2022下默认是有符号的类型,所以进行符号位扩展。所以在寄存器中ff扩展为 ff ff ff ff

同理b在寄存器中也扩展为了ff ff ff ff

而c是一个无符号数,进行0扩展,所以就是00 00 00 ff

ff ff ff ff 按照%d的形式打印就是-1

00 00 00 ff按照%d的形式打印就是255
结果:

2.3
c 复制代码
#include <stdio.h>

int main()
{
	char a = -128;
	printf("%u\n",a);
	return 0;
}

-128一个字节时是1000 0000 由于a是有符号类型,符号位扩展为ff ff ff 80,按照无符号打印,就是4,294,967,295 - 127 = 4,294,967,168。
结果:

c 复制代码
#include <stdio.h>
int main()
{
	char a = 128;
	printf("%u\n",a);
	return 0;
}

按理来说,char类型的变量表示的数值范围是0~127,-1~-128然而这里却用将128赋值给了a,那么实际是怎么运算的呢?

其实赋值操作的时候是把四字节数据截断赋值给一个字节,虽然char类型不能表示128,但是int类型可以,int 类型的128用4字节表示就是00 00 00 80。截断之后就变成了80,然后按照%u打印,先将80进行整型提升,符号位是1,那就是ff ff ff 80,按照%u打印,就是4,294,967,168和上面一样。
运行结果:

2.4
c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
	char a[1000];
	for(int i = 0;i < 1000;i++)
	{
		a[i] = -1 - i;
	}
	printf("%zd\n",strlen(a));
	return 0;
}

开辟一块空间,然后赋值。内存示意图:

问:什么时候数组a里的元素值等于0?

补码数值示意图:

char类型表示的负数范围是-1~-128那-129会被如何解释?-129首先赋值是四字节的-129截断成一字节:ff ff ff 7f,也就是7f,也就是说,负数的值小于-128之后就会像图片一样往0靠近。

所以从-1开始递减到0一共有256个数字,其中0不计,所以strlen就是255。
运行结果:

2.5
c 复制代码
#include <stdio.h>
unsigned char i = 0;
int main()
{
	for(i = 0;i <= 255;i++)
	{
		printf("hello world\n");
	}
    return 0;
}

所谓i++,其实是,i = i + 1;是一个赋值操作,unsigned char 类型的 i 表示的最大数应该是255,那么256是怎么表示的呢?四字节的256也就是00 00 01 00然后发生截断变成00,也就是说,对i一直++它的值也会像这个环一样循环:并且判断i <= 255的时候,进行了0扩展,所以寄存器中使用的值和它的值一样。

所以这个程序是个死循环。

运行结果:死循环打印hello world

c 复制代码
#include <stdio.h>
int main()
{
	unsigned int i;
	for(i = 9;i >= 0;i--)
	{
		printf("%u\n",i);
	}
	return 0;
}

和上面一样,i的值不会被解释成小于0的数,所以这也是个死循环。先打印9 ~ 0后打印极大的数递减。其实就是2^32 - 1开始递减。

2.6
c 复制代码
#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;
}

内存示意图:小端字节序,x86地址。

ptr1[-1]应该是00 00 00 04

*ptr2应该是02 00 00 00
运行结果:

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

浮点数的表示:

一张图搞清楚:

上面的是float的存储,下面的是double,至于long double 类型,根据平台不同大小不一样。

  • S:0表示正数,1表示负数

  • M:当E不为全0时,M表示为1.M,当E为全0时,M表示为0.M,E为全1时,如果M为全0就表示无穷大,否则就是NaN:Not a Number。

  • E:E是一个无符号整数,但是指数有负数,所以存入内存的E的值是真实值+8/11位指数位的中间数(127/1023)。全0时为-126,全1时不为128。

为什么E为全0的时候,M就要表示为0.M呢?

我们来看两种方案:

方案1:

E为全0时,M表示为0.M

最小规格化数:1.0*2-126

最大非规格化数:0.111...11*2-126

最小非规格化数:0.000...000*2-126

方案2:

E为全0时,M依然表示为1.M

最小规格化数:1.0*2-126

最大非规格化数:1.111...111*2-127

最小非规格化数:1.000...000*2-127

对比之下我们可以发现,方案1和方案2都实现了规格化数和非规格化数的过渡,但是方案1可以一直表示接近甚至是等于0的值。而方案二却因为要保证1.M而无法表示接近或者等于0的值。

什么是规格化和非规格化?

规格化就是保证精度但是牺牲表示范围,非规格化就是丢失一定的精度去保证表示更大的范围。

例题
c 复制代码
#include <stdio.h>
int main()
{
	int n = 9;
	float *pFloat = (float*)&n;
	printf("n的值为:%d\n",n);
	printf("*pFloat的值为:%f\n",*pFloat);
	
	*pFloat = 9.0f;
	printf("n的值为:%d\n",n);
	printf("*pFloat的值为:%f\n",*pFloat);
	
	return 0;
}

00 00 00 09:0000 0000 0000 0000 0000 0000 0000 1001

S:0

E:0000 0000

M:0000 0000 0000 0000 0001 001

此时E为全0,M表示0.M,也就是0.0000 0000 0000 0000 0001 001 * 2-126。而float默认打印小数点后六位所以打印结果应该是0.000000

*pFloat = 9.0f;

9.0表示为二进制就是1001.0也就是1.001*23,内存中就是:

S:0

E:1000 0010

M:0010 0000 0000 0000 0000 000

也就是:0100 0001 0001 0000 0000 0000 0000 0000 十六进制:41 10 00 00

打印成%d就是1,091,567,616。
运行结果:

相关推荐
三*一9 分钟前
Mapbox‑GL JS 多边形折点编辑工具:实现原理、功能拆解与踩坑总结
开发语言·javascript·ecmascript
YSL07012421 分钟前
C++基础
开发语言·c++
若亦_28 分钟前
Java中关于this的三种用法
java·开发语言·intellij-idea
2601_968793141 小时前
Python Requests 轻量自动化:无需浏览器,实现真人级账号浏览模拟
开发语言·python·自动化
只愿云淡风清1 小时前
正则表达式学习笔记 02:基础语法
笔记·学习·正则表达式
eggwyw1 小时前
关于C++为什么不加入垃圾回收机制解析
开发语言·c++
曦樂~11 小时前
Cpp面筋
c语言·c++
希和202712 小时前
考研英语阅读词汇笔记|城市野径、抄近道步道(非正式小路)
笔记·考研
来让爷抱一个12 小时前
Python 异步里混用同步库,事件循环被卡住的全过程
开发语言·python
一个人旅程~15 小时前
关于imac27inch因改短用户名导致混合硬盘和系统崩溃的问题分析与故障解决
经验分享·笔记·macos·新浪微博