案例分析-信息的表示与处理

文章通过实例分析了编程中整数溢出、精度损失以及时间表示的局限性,展示了32位系统中int类型和时间表示的问题,以及如何通过数据类型调整和类型转换来解决问题。同时,还讨论了数组越界问题及其影响。

案例分析-1:求平方的迷惑(数的表示范围)

解密1001.c(源代码见“资料”中的“演示代码”包)
请编译并运行该程序,分析为什么5万的平方,50万的平方,500万的平方是这样的值?
 

 

在这个程序中,由于int类型有大小限制,当整数值变得非常大时,会发生整数溢出和截断。在32位和64位系统上,int通常是4个字节,即32位的,这意味着它可以表示的最大正整数值是2^31 - 1(即2147483647)。
假设我们在一个32位系统上运行此程序,in将占用4个字节。当i的值在for循环中增加到某个点时,i*i的结果将超出int能表示的范围,因此会发生溢出。
具体来说,当i达到50000时,i * i的结果接近int的最大值。当i增加到500000时,i * i的结果已经超过了int的最大值,因此发生了溢出。溢出后的值不再是原来的平方值,而是变成了一个完全不同的数。当i进一步增加到5000000时,溢出的结果变成了负数,这是因为整数溢出是按照模2^32的方式进行并截断的。
这种情况当然也可以解决,

方法一:我们可以使用更大的整数类型,比如long long,因为它是8个字节,即64位的,可以表示更大的数值范围。修改代码如下:

 

方法二:可以直接在printf后面把i和i*i强制转换成浮点型(float)i和(float)i*i,也是一种解决方案:

#include "stdio.h"

int main()
{
	int i;
	printf("整数占用%d个字节\n",sizeof(int));
	for(i=5;i<50000000;i*=10)
		printf("i=%.10f,i*i=%.10f\n",(float)i,(float)i*(float)i);
	return 0;
}

但会出现精度丢失现象,这时候把float换成double就可以完美解决了:

#include "stdio.h"

int main()
{
	int i;
	printf("整数占用%d个字节\n",sizeof(int));
	for(i=5;i<50000000;i*=10)
		printf("i=%.10lf,i*i=%.10lf\n",(double)i,(double)i*(double)i);
	return 0;
}

案例讨论-2:2038年是世界末日? 

请看下面这个帖子,你相信2038年是世界末日吗?试分析为什么手机日期设置出现这种怪事。

对于多数手机来说,处理器都是32位的,用来表示时间的变量通常是一个32位的整数。这个整数一般表示从某个起始时间点(例如1970年1月1日00:00:00 UTC,即Unix时间戳的起点)开始的秒数。
然而,32位整数有一个最大的表示范围。利用我们这节课所学的知识,一个无符号的32位整数最大可以表示到2^32-1,即4294967295。如果用这个数来表示从1970年1月1日以来的秒数,我们会发现这个数值对应的日期大约是2038年1月19日。当时间到达这个点时,32位整数就会溢出,并且高位发生截断,回到0,这会导致计算机系统中的时间突然跳回到1970年。
因此,如果尝试将时间设置到2038年之后时,由于32位系统内部使用32位整数来表示时间这个硬件问题,它无法处理超过这个范围的值,所以时间无法继续增加。
当然,扯一些题外话,至于世界末日的传说,这完全是一种误解和谣言。2038年的这个问题仅仅是计算机系统内部表示时间的一个局限性,与任何灾难性事件无关。当计算机系统的时间到达2038年1月19日时,并不会发生任何特别的事情,除了时间可能会回滚到1970年之外。这也告诉我们学好计算机系统至关重要(doge)
言归正传,这个问题也是可以被解决的。为了解决这个问题,现在的多数计算机系统和手机操作系统已经开始使用64位整数来表示时间(比如我手机的天玑8100八核处理器),这样可以大大增加时间的表示范围,避免在可预见的未来出现时间溢出的问题。因此,对于使用64位系统的设备来说,这个问题已经不再存在。 

案例讨论-3:求负也会错? 

解密1003.c(源代码见“资料”中“演示代码”包)

请编译运行该程序,分析d为什么输出这样的值?

在大多数系统上,char类型的范围是1个字节,即8位,从-128到127(有符号数),或从0到255(无符号数)。在下图这个程序中,char c=-128; 这行代码将变量c初始化为-128。这是char类型可以表示的最小值(有符号数中)。char d=-c;;这行代码取c的相反数并将其赋值给d。但是,这里有一个非常非常重要的点,也是上课讲过的,在C语言中,整数类型的相反数是通过二进制补码形式来表示的。对于-128的二进制补码表示,它通常是这样的(假设一个字节,即8位):10000000
取这个值的相反数(即求其补码),在二进制补码系统中,实际上会得到相同的值,还是10000000,因为-128是其自身的相反数。这是因为-128是char类型可以表示的最小值,没有比它更小的值可以表示其相反数。因此,d的值仍然是-128。
最后,printf("c=%d,d=%d\n",(int)c,(int)d); 这行代码将c和d的值以整数形式打印出来。由于c和d都是char类型,而printf函数期望的是int类型的参数,因此我们通过(int)进行了类型转换。原本的10000000,被转换成了11111111 11111111 11111111 10000000(如下图所示,因为int是32位,所以我们选择DWORD)
所以,程序的输出将是:c=-128,d=-128。这个输出是正确的。 

案例讨论-4:“我”迷路了 

1004.c(源代码见“资料”中的“演示代码”包)

请编译并运行该程序,观察运行结果,并尝试分析为什么?

当编译并运行这个程序后,看不到任何输出,因为main函数中的sum(a, 0);调用没有将结果用于任何操作(比如打印)。然而,观察发现,程序并不是运行立刻就结束了的,而是响应了一段时间后才return 0。需要注意,sum函数中len的类型是unsigned,即无符号整数。当len为0时,循环条件i<=len-1实际上等价于i<=-1。但由于len是unsigned类型,len-1的结果会是一个非常大的无符号整数(因为无符号整数不会发生下溢,而是会环绕到其取值范围的最大值),即UMax,2^31-1。因此,i<=len-1这个条件对于任何非负的i都是true,这会导致一个很长的循环,并且在数组a的1、2、3之后可能出现段错误 

简单来说,就是数组越界越的太离谱了。可以把0换成10等比较小的数字,虽然也会越界,但就不那么离谱了...

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一二爱上蜜桃猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值