绝大多数C语言编程教材在介绍到数组时都会提到:数组下标是从 0 开始编号的,使用数组时一定不能超出定义的元素个数。这当然没什么好说的,初学者看到这句话,一般都会遵守。
奇怪的C语言数组
不过,总有人乐意折腾,小明发现在C语言编程里定义了数组后,使用数组即使超出了最大的元素个数,也能得到正确的结果,他的 C 语言代码是下面这样的:
#include <stdio.h>
int main()
{
int a[5] = {0};
int i;
for(i=1; i<=5; i++)
a[i] = i*2;
for(i=1; i<=5; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}
以上 C 语言代码编译后,运行结果如下:
# ./a.out
a[1] = 2
a[2] = 4
a[3] = 6
a[4] = 8
a[5] = 10
这样的结果让小明困惑不已,到底是为什么呢?现在我们将C语言比作“一位仁慈的皇帝”,它赏赐给了每一个王爷(进程)一块领地(内存)。王爷如何管理领地,皇帝就不管了,但是每一个王爷都不能侵犯别人的领地(进程不能直接使用别的进程内存),否则就杀死侵犯者(进程引用非法指针会引发“段错误”,进程就死(退出运行)了)。
王爷有感于皇帝的仁慈,颁布了如下法令:在本王领地里,臣民们可以自由使用土地。这条法令其实已经可以解答小明的困惑了,臣民 a 去王府登记,领了 5 亩地,a 自然可以使用 a[0] 到 a[4] 这 5 亩地。
臣民 a 真的可以随意使用土地吗?
但是现在王国里只有 a 一个人,所以其它土地暂时都是无主的,按照法令,“臣民们可以自由使用土地”,所以 a 使用 a[5] 这块无主地,王爷也是不管的。那 a 就嚣张了,它还使用了 a[6] 到 a[10] 这几块地,请看如下C语言代码:
#include <stdio.h>
int main()
{
int a[5] = {0};
int b[10] = {0};
int i;
for(i=1; i<=10; i++)
a[i] = i*2;
for(i=1; i<=10; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}
编译执行,得到如下结果:
# ./a.out
a[1] = 2
a[2] = 4
a[3] = 6
a[4] = 8
a[5] = 10
a[6] = 12
a[7] = 14
a[8] = 16
a[9] = 18
a[10] = 20
似乎一切都很正常。细心的朋友应该能够看到,王国里又来了新居民 b,它去王府登记,打算申请 10 亩地种玉米。王府查了登记簿,发现之前只有 a 申请了 5 亩地,所以就把 a 旁边的 10 亩地分配给了 b。拿到地的 b 高高兴兴的把地犁了一遍(int b[10] = {0};),打算种玉米。现在,我们将C语言代码稍作修改,把 b 的土地情况也打印出来:
// 将
printf("a[%d] = %d\n", i, a[i]);
// 修改为
printf("a[%d] = %d, b[%d] = %d\n", i, a[i], i-1, b[i-1]);
再编译C语言程序,运行结果如下:
b 辛辛苦苦犁好的地,就这么被 a 种上它自己的东西了。其实出现这种情况,从某种程度上来说也是必然的。想想看,a 使用了 a[5] 到 a[10] 这些无主地时,并没有去王府登记,所以王府根本不知道 a 使用了这些地。当居民 b 来申请土地时,非常有可能把这些土地分配给 b。这就导致了冲突。
总结一下
现在已经能够解答小明的困惑了:C语言是一门非常自由的语言,它给了程序员最大的自由,它相信每个C语言程序员都是高手,都知道自己在做什么,在C语言中,高手们使用 p[-1] 这种访问数组元素的方式也并不是没有。
现在来考虑 a 和 b 的冲突:如果 a 和 b 私下商量好,b 愿意把自己的一部分土地让给 a 使用,那即使冲突也没有什么问题,王国内依然一派和气。
但是仍然不建议像 a 那样霸道的使用别人的土地,因为很难遇到像 b 这么好说话的居民。另外,假如皇帝要求王爷进贡 10 亩玉米,王爷查看土地登记簿,发现 b 已经申请了 10 亩土地种玉米,就放心了。结果,b 的一部分土地被 a 拿去种别的东西了,导致王爷无法完成皇帝的任务被杀头,整个王国就都完蛋了。