当你取消引用一个未初始化的指针时,你调用undefined behavior。
虽然这通常会导致崩溃,但不一定非要如此。这就是为什么它被称为未定义的行为。程序可能会崩溃,可能会以意想不到的方式运行,或者(如您所见)它可能看起来正常工作。这种行为可能会随着看似无关的代码更改而改变,例如添加一个或多个局部变量。
这也意味着你不能依赖任何特定的行为。如果你使用不同的编译器,或者在不同的机器上构建,你会得到不同的结果。
让我们进一步说明未定义的行为。当我运行你的代码时,我遇到了分段错误,而对你来说它似乎运行正常。
下面是我在Valgrind下运行时得到的输出:
==1047== Memcheck, a memory error detector
==1047== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==1047== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==1047== Command: /tmp/x1
==1047==
hello
==1047== Conditional jump or move depends on uninitialised value(s)
==1047== at 0x3FA445345F: _IO_vfscanf (in /lib64/libc-2.5.so)
==1047== by 0x3FA445DCAB: scanf (in /lib64/libc-2.5.so)
==1047== by 0x4004F2: main (x1.c:6)
==1047==
==1047== Use of uninitialised value of size 8
==1047== at 0x3FA44534D3: _IO_vfscanf (in /lib64/libc-2.5.so)
==1047== by 0x3FA445DCAB: scanf (in /lib64/libc-2.5.so)
==1047== by 0x4004F2: main (x1.c:6)
==1047==
==1047==
==1047== Process terminating with default action of signal 11 (SIGSEGV)
==1047== Bad permissions for mapped region at address 0x400520
==1047== at 0x3FA44534D3: _IO_vfscanf (in /lib64/libc-2.5.so)
==1047== by 0x3FA445DCAB: scanf (in /lib64/libc-2.5.so)
==1047== by 0x4004F2: main (x1.c:6)
==1047==
==1047== HEAP SUMMARY:
==1047== in use at exit: 0 bytes in 0 blocks
==1047== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==1047==
==1047== All heap blocks were freed -- no leaks are possible
==1047==
==1047== For counts of detected and suppressed errors, rerun with: -v
==1047== Use --track-origins=yes to see where uninitialised values come from
==1047== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)
您可以从此输出中看到正在使用未初始化的变量,这随后会导致分段违规。
在 gdb 下运行,我明白了:
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-45.el5)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /tmp/x1...done.
(gdb) start
Temporary breakpoint 1 at 0x4004e0: file /tmp/x1.c, line 8.
Starting program: /tmp/x1
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x2aaaaaaab000
Temporary breakpoint 1, main () at /tmp/x1.c:8
8 scanf("%s",a[0]);
(gdb) p a
$1 = {0x400520 "L\211d$\340L\211l$\350L\215%\223\001 ",
0x4003bb "H\203\304\b\303\377\065\312\004 ",
0xca000000000001 <Address 0xca000000000001 out of bounds>,
0x400557 "H\215\005f\001 ", 0x0, 0x3fa421cbc0 "",
0x400520 "L\211d$\340L\211l$\350L\215%\223\001 ", 0x0,
0x7fffffffe860 "\001", 0x0}
(gdb) step
hello
Program received signal SIGSEGV, Segmentation fault.
0x0000003fa44534d3 in _IO_vfscanf_internal () from /lib64/libc.so.6
(gdb)
记下a 包含的内容。现在,我将做一个小改动:
#include<stdio.h>
int main()
{
int x[100];
char *a[10];
int y[100];
scanf("%s",a[0]);
printf("%s",a[0]);
return 0;
}
我在a 前后添加了一个局部变量。在一个表现良好的程序中,这不会改变任何事情。但是当出现未定义的行为时,所有的赌注都被取消了。
在 Valgrind 下运行:
==1392== Memcheck, a memory error detector
==1392== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==1392== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==1392== Command: /tmp/x1
==1392==
hello
==1392== Conditional jump or move depends on uninitialised value(s)
==1392== at 0x3FA445345F: _IO_vfscanf (in /lib64/libc-2.5.so)
==1392== by 0x3FA445DCAB: scanf (in /lib64/libc-2.5.so)
==1392== by 0x4004F8: main (x1.c:8)
==1392==
==1392== Conditional jump or move depends on uninitialised value(s)
==1392== at 0x3FA4443D1C: vfprintf (in /lib64/libc-2.5.so)
==1392== by 0x3FA444CD09: printf (in /lib64/libc-2.5.so)
==1392== by 0x40050E: main (x1.c:9)
==1392==
(null)==1392==
==1392== HEAP SUMMARY:
==1392== in use at exit: 0 bytes in 0 blocks
==1392== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==1392==
==1392== All heap blocks were freed -- no leaks are possible
==1392==
==1392== For counts of detected and suppressed errors, rerun with: -v
==1392== Use --track-origins=yes to see where uninitialised values come from
==1392== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)
这次没有段错误,但是打印的是“(null)”而不是输入字符串“hello”。
gdb 下:
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-45.el5)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /tmp/x1...done.
(gdb) start
Temporary breakpoint 1 at 0x4004e3: file /tmp/x1.c, line 8.
Starting program: /tmp/x1
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x2aaaaaaab000
Temporary breakpoint 1, main () at /tmp/x1.c:8
8 scanf("%s",a[0]);
(gdb) p a
$1 = {0x0, 0x7fffffffe5d0 "", 0xf63d4e2e <Address 0xf63d4e2e out of bounds>,
0x7fffffffe760 "0\005@", 0x7fffffffe778 "", 0x3fa4403a90 "", 0x0,
0x2aaaaaaaf630 "\021\003@", 0x2aaaaaaaf0f0 "", 0x4002ff "__libc_start_main"}
(gdb) step
hello
9 printf("%s",a[0]);
(gdb)
10 return 0;
(gdb)
11 }
(gdb)
0x0000003fa441d9f4 in __libc_start_main () from /lib64/libc.so.6
(gdb)
Single stepping until exit from function __libc_start_main,
which has no line number information.
(null)
Program exited normally.
(gdb) quit
特别注意a 在每种情况下的内容。对于原始程序,a[0] 包含0x400520,而在修改后的程序中a[0] 包含0x0。
总而言之,对于未定义的行为,没有任何保证。为避免这种情况,请务必编译所有警告(-Wall -Wextra 用于 GCC)并使用 Valgrind 之类的内存检查器来捕获您正在读取或写入不应该写入的位置的情况。