我的 C 程式以 segfault 結尾 - valgrind

讓我們有一個基本的失敗計劃:

#include <iostream>

void fail() {
    int *p1;
    int *p2(NULL);
    int *p3 = p1;
    if (p3) {
        std::cout << *p3 << std::endl;
    } 
}

int main() { 
    fail();
}

構建它(新增 -g 以包含除錯資訊):

g++ -g -o main main.cpp

跑:

$ ./main
Segmentation fault (core dumped)
$

讓我們用 valgrind 除錯它:

$ valgrind ./main
==8515== Memcheck, a memory error detector
==8515== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8515== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==8515== Command: ./main
==8515==
==8515== Conditional jump or move depends on uninitialised value(s)
==8515==    at 0x400813: fail() (main.cpp:7)
==8515==    by 0x40083F: main (main.cpp:13)
==8515==
==8515== Invalid read of size 4
==8515==    at 0x400819: fail() (main.cpp:8)
==8515==    by 0x40083F: main (main.cpp:13)
==8515==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==8515==
==8515==
==8515== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==8515==  Access not within mapped region at address 0x0
==8515==    at 0x400819: fail() (main.cpp:8)
==8515==    by 0x40083F: main (main.cpp:13)
==8515==  If you believe this happened as a result of a stack
==8515==  overflow in your program's main thread (unlikely but
==8515==  possible), you can try to increase the size of the
==8515==  main thread stack using the --main-stacksize= flag.
==8515==  The main thread stack size used in this run was 8388608.
==8515==
==8515== HEAP SUMMARY:
==8515==     in use at exit: 72,704 bytes in 1 blocks
==8515==   total heap usage: 1 allocs, 0 frees, 72,704 bytes allocated
==8515==
==8515== LEAK SUMMARY:
==8515==    definitely lost: 0 bytes in 0 blocks
==8515==    indirectly lost: 0 bytes in 0 blocks
==8515==      possibly lost: 0 bytes in 0 blocks
==8515==    still reachable: 72,704 bytes in 1 blocks
==8515==         suppressed: 0 bytes in 0 blocks
==8515== Rerun with --leak-check=full to see details of leaked memory
==8515==
==8515== For counts of detected and suppressed errors, rerun with: -v
==8515== Use --track-origins=yes to see where uninitialised values come from
==8515== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
$

首先我們關注這個塊:

==8515== Invalid read of size 4
==8515==    at 0x400819: fail() (main.cpp:8)
==8515==    by 0x40083F: main (main.cpp:13)
==8515==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

第一行告訴我們,段錯誤是由讀取 4 個位元組引起的。第二行和第三行是呼叫堆疊。這意味著無效讀取是在 main.cpp 的第 8 行的 fail() 函式上執行的,該函式由 main.cpp 的第 13 行呼叫。

看看 main.cpp 的第 8 行,我們看到了

std::cout << *p3 << std::endl;

但是我們先檢查指標,那有什麼不對?讓我們檢查另一個塊:

==8515== Conditional jump or move depends on uninitialised value(s)
==8515==    at 0x400813: fail() (main.cpp:7)
==8515==    by 0x40083F: main (main.cpp:13)

它告訴我們第 7 行有一個單元化變數,我們讀到它:

if (p3) {

這指向了我們檢查 p3 而不是 p2 的行。但 p3 怎麼可能未初始化?我們通過以下方式初始化:

int *p3 = p1;

Valgrind 建議我們重新執行 --track-origins=yes,讓我們這樣做:

valgrind --track-origins=yes ./main

valgrind 的論點就在 valgrind 之後。如果我們把它放在我們的程式之後,它將被傳遞給我們的程式。

輸出幾乎相同,只有一個區別:

==8517== Conditional jump or move depends on uninitialised value(s)
==8517==    at 0x400813: fail() (main.cpp:7)
==8517==    by 0x40083F: main (main.cpp:13)
==8517==  Uninitialised value was created by a stack allocation
==8517==    at 0x4007F6: fail() (main.cpp:3)

這告訴我們在第 7 行建立的未初始化值是在第 3 行建立的:

int *p1;

它指導我們未初始化的指標。