我们知道很多高级编程语言的编译器都会校验数组越界的问题,但是C语言的编译器不会,也就是说我们使用数组外的内存空间,但编译器不会出错。例如:
#include <stdio.h> int main(int argc, char **args) { int array[4] = { 0, 1, 2, 3 }; printf("%d\n%d\n%d\n%d\n%d\n%d\n", array[-1], array[0], array[1], array[2], array[3], array[4]); return 0; }
函数栈帧如下:
可以看到数组array的前一个地址单元存放的是printf的第一个形参char *fmt,而数组array的后一个地址单元存放的是%esp。C语言中在对数组操作时会根据数据下标计算出其内存地址,于是array[-1]就是char *fmt的值,array[4]就是%esp的值。运行结果:
-3786412 0 1 2 3 -3786560
程序运行成功,但结果很有问题,上面例子中只是对array越界读取其值,如果要对这些内存地址写入值的话,则编译通过,但运行失败。再来看一下:
#include <stdio.h> void func() { int array[4] = { 0, 1, 2, 3 }; array[4] = 0; } int main(int argc, char **args) { int i = 0; func(); printf("%d\n", i); return 0; }
运行结果:
Segmentation fault
原因很简单,array[4] = 0;这条语句修改了main函数%esp的值,而当func()执行结束返回到main函数时,%esp变成了0,也就是说main函数要使用printf()函数来显示i的值时,栈顶地址为0,就是说编译器认为栈顶地址为0,i在地址为4的地方。然而操作系统通常将低地址的内存保护起来,只为操作系统内核程序来使用,所以当普通程序试图访问低地址时则会出现“段访问异常”的错误。
接下来我们修改一下程序,去掉main函数中定义的i变量和printf()显示部分,并将func()函数中的array[5]的值减去5:
#include <stdio.h> void func() { int array[4] = { 0, 1, 2, 3 }; array[5] -= 5; } int main(int argc, char **args) { func(); return 0; }
此时程序同样可以编译通过,但是程序在运行时则会出现一个非常有趣的现象——无限循环,无法自行终止。我们还是通过反汇编和栈帧来分析原因:
080483f8 <main>: 80483f8: 55 push %ebp 80483f9: 89 e5 mov %esp,%ebp 80483fb: e8 cb ff ff ff call 80483cb <func> 8048400: b8 00 00 00 00 mov $0x0,%eax 8048405: 5d pop %ebp 8048406: c3 ret
当func()函数对array[5]操作时,实际上就是对main函数栈帧中的%eip操作,array[5] -= 5;相当于将%eip减5。在main()函数调用func()函数时,%eip为0x8048400也就是call指令的下一条指令所在的地址,将%eip压栈。当func()执行完毕时,ret指令将修改%eip其栈中内容,注意,此时%eip的值已经被减5,于是%eip的值为0x80483fb,也就是指令call 80483cb <func>,于是又将%eip设置为下一条指令的值,并将其压栈。再调用func(),func()中又修改了栈中存放%eip地址的值,将其减5,运行结束后ret到main()函数……
这是一个非常有意思的事,在被调用函数中修改了越界数组的值,结果导致了程序的无限循环。所以在编写程序时一定要注意数组越界的问题。当我们了解了C语言内在的编译原理和作用就会对这解决些表面上很奇怪又不好分析原因的问题有一定的帮助。
当然,我们在了解了这类问题之后可以使用不同的方式来读写与数组相邻的变量:
#include <stdio.h> int main(int argc, char **args) { int m = 5; int n = 7; int array[4] = { 0, 1, 2, 3 }; printf("%d %d\n", array[4], n); array[5] = 13; printf("%d %d\n", array[5], m); return 0; }
运行结果:
7 7 13 13
也就是说,操作array[4]就相当于操作变量n;而操作array[5]就相当于操作变量m。
Copyright © 2015-2023 问渠网 辽ICP备15013245号