身为一名程序员,或多或少都会了解一点 C 语言,我现在还清楚地记得,大一刚接触 C 语言时被它所支配的恐惧。C 语言无比强大,被称为「高级汇编语言」足以见得它的威力,也可以看出它经常与计算机底层打交道;它的指针部分更为精彩(也是最难的部分),那么我们就跳过它最难的部分,检查一下你对 C 语言掌握的程度。
下面是一个函数 sum_elements( ),它的作用是对给定的数组中所有元素求和并返回其值,按照代码中给定的值去执行,你认为会得到什么结果呢?
#include<stdio.h>
float sum_elements(float a[], unsigned length) {
int i;
float result;
result = 0;
for (i = 0; i <= length - 1; i++)
result += a[i];
return result;
}
void main()
{
float a[1] = { 0.1 };
float sum;
sum = sum_elements(a, 0);
printf("%f\n", sum);
}
当我们让 length = 0 时,想要得到的结果是 0.000000,但是运行时你会发现该程序会报出内存访问异常错误。你知道是什么原因出现这个错误吗?这就是检验你功底是否扎实的时候了,先仔细看看代码,好好想一想再继续往下看。
... ...
怎么样,知道是什么原因导致这段代码出现了我们预料之外的错误了吗?这里的 bug 是无符号整数(unsigned)导致的。
在 C 语言中,无符号整数是 4 个字节,1 个字节为 8 位,十进制数 0 用二进制表示为 0000 0000 0000 0000 0000 0000 0000 0000,计算机做减法是通过补码进行,补码为源码除符号位外各位取反再加一。-1 的补码为 1111 1111 1111 1111 1111 1111 1111 1111,计算 length - 1 (0 - 1) 就是求 length 与 -1 的补码之和,得到的结果为 1111 1111 1111 1111 1111 1111 1111 1111,因为之前定义形参的时候将 length 定义为无符号整数,所以 C 语言将计算结果按照无符号整数解释,得到的十进制数字为 4294967295(2^32 - 1)而不是我们想要的 -1,循环时 i 初值被赋为 0 ,一直小于这个数,所以循环会不断地进行,代码将试图访问数组 a 的非法元素,导致内存访问异常。
这个程序表面上一切正常,很符合正常人思路,数组下标不能为负数,因此形参 length 用无符号整数表示;停止条件 i <= length - 1 看上去也十分自然。但是将这两个条件组合在一起,意料之外的事情就发生了。C 语言确实很强大,但是如果我们的计算机基础知识不扎实,很可能出现各种奇奇怪怪的 bug。原来我一直觉得学那些枯燥无味的计算机基础知识没用,但是越往前走越发觉计算机基础知识很重要。书到用时方恨少,出了 bug 找不到。
现在这个 bug 的原因已经找到了,如何修改这个 bug 使得该程序能够顺利执行呢?一种方法是修改循环条件,改成 i < length ;另一种方法是将形参 length 定义为 int 类型。