C语言无符号数据读取

Truncation of Signed Numbers In C

作者 Leon Dong 日期 2017-05-29
C语言无符号数据读取

背景

像C语言这种语言对输入输出的要求极其严格,你必须要制定你读取的数据类型以及格式。现在的问题是如果你读取的时候指定了错误的数据类型,会发生什么?

案例分析

加入说你要读取64位无符号整数类型数据,所以你的程序可能大概是这样子的。

1
2
unsigned long val=0;
scanf("%lu",&val);

在这里面有两点需要注意,其一是%lu,这说明了我要读取的数据类型是long类型且无符号。另一点是变量val类型的声明一点要一致,否则读取出来的值会被截断。当然你如果读取short,用int来存储是不发生任何精度损失的。不过即使如此,这种类型不匹配的方式也是不推荐的。

现在的问题是,如果把程序写成这样会发生什么?

1
2
unsigned long val=0;
scanf("%ld",&val);

我们知道%ld也是读取long类型的数据,只不过数据是包含符号的。由于无符号类型的数据的取值范围是$[0,2^{64}-1]$,如果把这个范围分为两份,一部分对于有符号数的正数部分$[0,2^{63}-1]$,另一部分对于与负数部分$[2^{63}-1,2^{64}-1] \rightarrow [-2^{63},-1]$。

对于位于$[0,2^{63}-1]$的数据,在这个例子里面用%ld%%lu%都可以得到相同的结果。但是对于输入位于$[2^{63}-1,2^{64}-1]$的数据,%ld能正确读取么,它能自动的将这份数据转为负数读取进来么?如果是的话,那么%ld%读取的值也会是正确的。因为一个负的long型正数传给unsinged long 类型的变量时,会自动发生强转。

事实上,scanf("%ld",&val) 并不能正确的读取数据。对于$[2^{63}-1,2^{64}-1]$的数字,他将最高的二进制位自动当0处理,读取出来的数值永远在分$[0,2^{63}-1]$之间

从这个例子里面,我们可以看到C语言对输入输出的要求,在平时写程序的时候一定要注意这些细节。事实上,我们也是因为代码改来改去的,忘记去改输入了才发现的这个问题。