【问题标题】:Why doesn't using an unitialized pointer cause a segmentation fault?为什么使用未初始化的指针不会导致分段错误?
【发布时间】:2016-11-07 14:12:03
【问题描述】:

当运行这个程序时:

#include<stdio.h>

int main()
{
   char *a[10];
   scanf("%s",a[0]);
   printf("%s",a[0]);
   return 0;
}

它似乎运行良好,没有显示分段错误。

既然数组a的每个元素都是一个尚未初始化的指针(即a[0]),为什么程序没有显示分段错误?

【问题讨论】:

  • 因为“未定义的行为”意味着“任何事情都可能发生”,包括“程序完美运行”
  • 因为你很幸运
  • 因为你没有写 through 未初始化的指针,你写的是 over 指针的存储空间。我有 95% 的把握已经有一个更详细地涵盖了这个问题的答案,但我无法在脑海中找到它。
  • 这里真正的问题是,如果您知道行为,为什么还要编码?顺便说一句,main 签名至少应为int main(void)int main(int argc, char *argv[])
  • @EdHeal:我的观点恰恰相反——任何不会立即导致崩溃或段错误的 UB 都是不幸,因为你不会立即知道有一个问题。如果该 UB 在测试过程中被忽视并最终进入生产代码,那么它可能会潜伏数月或数年,直到边缘情况的正确组合导致其崩溃(或打开一个巨大的安全漏洞)。

标签: c


【解决方案1】:

当你取消引用一个未初始化的指针时,你调用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 之类的内存检查器来捕获您正在读取或写入不应该写入的位置的情况。

【讨论】:

    猜你喜欢
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 2014-01-10
    • 1970-01-01
    • 2013-10-25
    • 1970-01-01
    • 2011-11-22
    • 1970-01-01
    相关资源
    最近更新 更多