#include<stdio.h>
void main(){
int i, b[12];
for ( i = 0; i <= 12; i++ )
b[i] = 0;
}
这个程序短小精悍,区区5行即可说明问题。虽然表面上看来是数组下标越界错误,实际运行起来则是死循环而不会报错。虽然老早就纸上谈兵的知道原因,不过从没实际运行观察过。在网上小搜一下发现VC可以由C/C++代码编译时生成汇编代码,也可在调试时即时显示反汇编,今天就小试了一把。
开始是在Debug模式下试验的,此模式下编译器会自己在 i 和 b[12] 之间插入 2 Byte 的无用空间,貌似就是用来检测这种错误的,运行将要结束时被 VC 发现 b 变量存在问题报错。果然很强啊。
后来转到 Release 模式,编译生成代码如下:
_TEXT SEGMENT
_b$ = -52 ; size = 48
_i$ = -4 ; size = 4
_main PROC
; 1012 : {
push ebp
mov ebp, esp
sub esp, 52 ; 00000034H
; 1013 : int i, b[12];
; 1014 : for ( i = 0; i <= 12; i++ )
mov DWORD PTR _i$[ebp], 0
jmp SHORT
$LN3@main$LN2@main:
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
$LN3@main:
cmp DWORD PTR _i$[ebp], 12 ; 0000000cH
jg SHORT
$LN4@main
; 1015 : {
; 1016 : b[i] = 0;
mov ecx, DWORD PTR _i$[ebp]
mov DWORD PTR _b$[ebp+ecx*4], 0
; 1017 : }
; 1018 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
可以看到 b 的偏移是 -52 , i 的偏移是 -4 ,而 b[12] 即 -52+4*12=-4 刚好就是 i 的地址。运行一下果然不再报错直接死循环了
下面是调试时的反汇编:
void main()
{
00401800 push ebp
00401801 mov ebp,esp
00401803 sub esp,34h
int i, b[12];
for ( i = 0; i <= 12; i++ )
00401806 mov dword ptr [i],0
0040180D jmp main+18h (401818h)
0040180F mov eax,dword ptr [i]
00401812 add eax,1
00401815 mov dword ptr [i],eax
00401818 cmp dword ptr [i],0Ch
0040181C jg main+2Bh (40182Bh)
{
b[i] = 0;
0040181E mov ecx,dword ptr [i]
00401821 mov dword ptr b[ecx*4],0
}
00401829 jmp main+0Fh (40180Fh)
}
0040182B xor eax,eax
0040182D mov esp,ebp
0040182F pop ebp
00401830 ret
i 的地址是 0x0012ff78,b 是 0x0012ff48,相差 30H = 48 Byte =12 个 int 的空间。把这一段地址的内存显示出来单步跟踪运行即可看到 i 是如何被修改的,一目了然啊……
编译原理这么看起来直观多了,有空再找几个例子玩玩。