目次
1. 前言
在學習 C 語言或在開發現場使用時,遇到「Segmentation Fault(段錯誤,簡稱 segfault)」這類錯誤的經驗應該不少吧。當程式突然異常結束,螢幕上顯示不熟悉的錯誤訊息「Segmentation fault (core dumped)」時,往往會對發生了什麼感到困惑。 此錯誤是 C 語言的典型執行時錯誤,當對記憶體進行不正當操作時,作業系統會強制停止程式的執行。C 語言是一種讓程式設計師記憶體的強大語言,但若稍有失誤,就可能存取到未預期的記憶體區域,帶來危險。 本文將針對何在 C 語言中會發生 Segmentation Fault」「常見原因與具體範例」「發生時的調查與除錯方法」「防止再次發生的程式撰寫要點」等主題,為初學者提供易於理解的說明。目標是提供從想扎實掌握 C 語言基礎的學習者,到實際在現場開發的工程師,都能受益的內容。 希望能透過本文解決「出現 Segmentation Fault 程式無法執行」或「不知道該如何修正」等困擾,提供安全且高效進行 C 語言程式設計的提示。2. Segmentation Fault是什麼?
Segmentation Fault(段錯誤,簡稱:Segfault)是指當程式存取「本不該存取的記憶體區域」時,被作業系統強制終止的錯誤。C 語言程式中常見的「Segmentation fault」或「core dumped」等訊息,表示此現象已發生。 C 語言允許程式設計師自由操作記憶體,但也必須承擔其管理責任。使用指標變數直接指定記憶體區域並存取是其特點,但若存取的目標是未定義、無效或受保護的區域,作業系統會判斷不再允許程式執行。此時,系統將此視為「不正當存取」而產生 Segmentation Fault。 例如,在以下情況下常會發生 Segmentation Fault。- 參考了 NULL 指標或未初始化的指標時
- 存取已釋放(free 後)的記憶體時
- 超出陣列或指標範圍存取時
- 將值寫入寫入保護的記憶體區域(例如:字串常量)時
3. Segmentation Fault的主要原因
Segmentation Fault在C語言中因記憶體操作錯誤而頻繁發生。這裡將介紹特別常見的代表性原因,並附上具體範例。了解這些後,錯誤發生時的故障排除將大幅加快。NULL指標或未初始化指標的參照
C語言中,僅宣告指標變數其內容仍未定義(垃圾值)。若直接參照,會存取到不正確的記憶體位址,導致Segmentation Fault。若誤參照以NULL初始化的指標亦同。int *p = NULL;
*p = 10; // ← 因NULL參照而產生的段錯
已釋放記憶體(懸掛指標)之存取
使用malloc
或calloc
等動態配置的記憶體區域,若在以free
釋放後仍存取該區域,會觸及已無效的位址,成為段錯的原因。int *q = malloc(sizeof(int));
free(q);
*q = 5; // ← 存取已釋放記憶體導致段錯
陣列或指標的範圍外存取(緩衝區溢位)
若陣列的索引寫錯而存取到範圍外,程式會觸及未預期的記憶體區域,這也是段錯的原因。C語言不會自動進行索引檢查,因而成為錯誤的溫床。int arr[5];
arr[10] = 1; // ← 範圍外存取導致段錯
堆疊溢位(巨型陣列或過深遞迴)
若在函式內定義過大的陣列,或遞迴呼叫過深,會超出程式可使用的堆疊領域,導致作業系統產生段錯。void func() {
int arr[1000000]; // ← 巨型陣列導致堆疊溢位
func(); // ← 遞迴呼叫無法停止導致段錯
}
寫入禁止的記憶體區域之寫入
若對文字常值字串或常數陣列等本應不可寫入的區域嘗試寫入值,也會產生Segmentation Fault。char *str = "hello";
str[0] = 'H'; // ← 向寫入禁止區域寫入導致段錯
這些模式即使熟悉C語言也容易疏忽。特別是在涉及指標操作或動態記憶體管理的程式碼中,逐一仔細找出原因是很重要的。4. 常見的發生案例與NG模式
Segmentation Fault 是一種因小失誤或疏忽而常發生的錯誤。本章將舉出實際常見的程式碼範例,說明為何會產生錯誤。此外,也會提及在現場常讓人困惑的「有無 printf 會改變執行結果」現象。NULL 指標與未初始化指標的NG範例
int *ptr; // 未初始化
*ptr = 100; // 參考垃圾位址→段錯誤
或者int *ptr = NULL;
printf("%dn", *ptr); // 參考 NULL→段錯誤
指標必須先初始化,若為 NULL 則不要存取。free 後的記憶體存取(懸掛指標)
int *data = malloc(sizeof(int));
*data = 50;
free(data);
printf("%dn", *data); // 參考已釋放的區域→段錯誤
釋放後務必將 data = NULL;
,以避免之後的存取。陣列範圍外存取(緩衝區溢位)
int arr[3] = {1, 2, 3};
arr[3] = 10; // 陣列僅有 0〜2。範圍外存取會導致段錯誤
C 語言不會自動偵測範圍外存取。必須特別注意 for 迴圈的條件式。關於「有無 printf 會改變執行結果」的現象
有些錯誤會出現「加入 printf 後錯誤不再出現/反而出現」的現象。這是因為在未初始化指標或堆疊變數使用不明確時,printf 會微妙改變記憶體配置,導致錯誤暫時不會顯現(或相反地顯現)。在此情況下,根本原因不是 printf,而是「未初始化變數」或「範圍外存取」。除錯時不要以「printf 能跑就沒問題」為依據,務必確認初始化與存取範圍的正確性。總結:現場常見的NG模式避免方法
- 指標必須先初始化並檢查 NULL
- free 後將指標設為 NULL,防止再次存取
- 對陣列與記憶體的範圍外存取保持極度注意
- 即使 printf 暫時消除症狀,也不要掉以輕心
5. Segmentation Fault 發生時的除錯手法
Segmentation Fault 發生時,「哪裡」與「為何」錯誤產生,快速定位非常重要。這裡介紹在 C 語言開發現場有用的代表性除錯方法。以即使是初學者也能實作的步驟與工具為中心說明。gdb 除錯(基本使用方式)
gdb(GNU Debugger)是 C 語言程式除錯最常用的工具。 依照以下步驟,即可找出 Segmentation Fault 的發生位置。- 編譯時加入除錯資訊
gcc -g sample.c -o sample
- 使用 gdb 執行程式
gdb ./sample
- 在 gdb 提示字元輸入「run」並執行
(gdb) run
- 發生段錯時,使用「bt」(backtrace)檢查堆疊追蹤
(gdb) bt
valgrind 記憶體錯誤偵測
valgrind 是能自動偵測 heap 與指標不當操作的強大工具。 特別對「使用已釋放的記憶體」與「讀寫未初始化區域」等情況有效。- 執行範例
valgrind ./sample
如果結果出現「Invalid read」「Invalid write」等,即表示該處為錯誤來源。printf 除錯/assert 的活用
使用「printf」來確認變數的值或處理的進度,也是簡便且有效的方法。 但如前章所述,printf 本身可能改變記憶體配置,暫時隱藏症狀,因此在根本解決錯誤前需特別留意。 此外,使用「assert」時,若條件不成立會立即停止程式,有助於早期發現錯誤。#include <assert.h>
assert(ptr != NULL); // 若 ptr 為 NULL 則異常結束
核心轉儲的活用方法
依作業系統不同,程式在因 Segmentation Fault 結束時,會自動輸出稱為「核心轉儲」的記憶體快照。 若在 gdb 中載入此檔案,即可詳細解析程式結束時的記憶體狀態與變數內容。gdb ./sample core
總結:找出錯誤位置是最重要的要點
- 發生 Segmentation Fault 時,首先調查「在哪裡發生」
- 熟練使用 gdb、valgrind 等工具,可大幅加速原因追查
- 手動的 printf 除錯亦有效,但別忘記解決根本原因
6. 防止再發與安全的 C 語言編碼要點
Segmentation Fault 即使已修正,只要大意仍有可能多次再次發生的風險。因此重要的是「安全的 C 語言編碼習慣化」。本章整理了在實務中也有用的防止再發的技巧與要點。指標的初始化與 NULL 檢查的徹底
在宣告指標後,基本上必須立即以NULL
或適當的位址進行初始化。未初始化的指標會成為不明原因的段錯(Segmentation Fault)的根源。另外,在使用指標之前務必進行 NULL 檢查。int *ptr = NULL; // 初始化
if (ptr != NULL) {
*ptr = 10;
}

動態記憶體管理的規則化
使用malloc
或 calloc
取得記憶體時,應養成在失敗時(返回值為 NULL)也要妥善檢查的習慣。另外,free
後的指標務必賦值為 NULL
,以防止再次使用,從而避免因懸掛指標導致的段錯。int *data = malloc(sizeof(int));
if (data == NULL) {
// 錯誤處理
}
free(data);
data = NULL;
陣列與記憶體的邊界檢查與 assert 的活用
在存取陣列或緩衝區時,務必確認在範圍內。另外,利用assert
可以在開發時提前偵測「不可能」的條件。#include <assert.h>
int arr[5];
int idx = 4;
assert(idx >= 0 && idx < 5); // 超出範圍則立即停止
arr[idx] = 10;
靜態分析工具與 Sanitizer 的使用
在最近的開發環境中,已配備靜態分析(static analyzer)與執行時錯誤偵測工具(Sanitizer)等,可自動偵測 bug 的溫床。例如「AddressSanitizer」或「Clang Static Analyzer」等,對於偵測堆疊或堆的非法存取非常有效。# AddressSanitizer 的範例(gcc/clang)
gcc -fsanitize=address -g sample.c -o sample
./sample
編碼規範與審查的徹底
無論是個人開發或團隊開發,都應制定編碼規範,使指標與記憶體操作保持一致性。另外,務必接受第三方審查,以防止意想不到的錯誤與遺漏。總結:以習慣與工具防止事故
- 徹底初始化、NULL 檢查、free 後的 NULL 代入
- 以範圍檢查與 assert 防止非法存取
- 積極活用靜態分析、Sanitizer 等最新工具
- 程式碼審查與結對程式設計也是有效的對策
7. OS 與開發環境的差異與注意事項
Segmentation Fault 是在任何 OS 或開發環境中都會共同發生的錯誤,但「錯誤訊息的表現」以及「錯誤的再現性」與「除錯方法」等會因使用的 OS 或編譯器而略有不同。以下將說明各代表性環境的差異與注意事項。gcc 與 clang(Linux/macOS 環境)
在 C 語言開發中廣泛使用的 gcc 與 clang,是 Linux 與 macOS 的標準編譯器。 在這些環境中,發生段錯時通常會顯示「Segmentation fault (core dumped)」的錯誤訊息。 此外,還可以使用 gdb、valgrind 等強大的除錯與驗證工具,因而容易追查原因與確認再現性。- 範例:
Segmentation fault (core dumped)
Visual Studio/Windows 環境
在 Windows 進行 C 語言開發時,Visual Studio 與 MinGW 等編譯器是主流。 在此環境中,發生段錯時會顯示「存取違反(Access violation)」或「0xC0000005」等 OS 專屬的錯誤訊息。- 範例:
Exception thrown at 0x00401020: Access violation reading location 0x00000000.
- 使用除錯器(如 Visual Studio Debugger 等),即可確認堆疊追蹤與變數狀態。
macOS 特有的行為
macOS 以 clang 為標準,但因安全功能(如 System Integrity Protection 等),可存取的記憶體區域較 Linux 更嚴格受限。 因此,原本在 Linux 上正常執行的程式碼在 macOS 可能會出現段錯,故在不同環境下測試相當重要。除錯工具的差異與選擇
- Linux/macOS:gdb、lldb、valgrind、AddressSanitizer 等,選項眾多
- Windows:Visual Studio Debugger、Dr. Memory 等,提供 Windows 專用的除錯工具
跨平台開發的注意事項
因各 OS 對「未定義行為」的處理可能不同,開發時盡量遵循「符合標準 C 的寫法」。 此外,在發佈或釋出前務必於多個環境進行運作確認,以防止特定 OS 專屬的錯誤。總結:了解環境差異,撰寫穩健的程式碼
- 段錯發生時的錯誤訊息與調查方法因 OS 與編譯器而異
- Linux/macOS 除錯工具豐富,Windows 以 Visual Studio 為中心
- 透過多環境測試與驗證防止再次發生並提升品質
8. 總結
在本文中,我們系統性地說明了在 C 語言中常見的 Segmentation Fault(段錯誤、Segfault),包括其定義與機制、主要原因、易發生的程式碼範例,以及發生時的除錯方法與防止再發的對策。 Segmentation Fault 是 C 語言高度自由度所伴隨的風險,「能自行管理記憶體」的優勢,有時也會成為「導致致命錯誤」的弱點。從新手到資深開發者,只要大意就可能遭遇此問題。然而,只要了解原因與模式,平時養成正確的程式撰寫習慣,就能事先防止大多數的 Segfault。 特別重要的是以下要點。- 徹底指標的初始化與 NULL 檢查
- 確認陣列與記憶體操作時的範圍
- free 後的指標管理,以及動態記憶體使用時的規範化
- 積極活用靜態分析工具與除錯器
- 在多個作業系統與編譯器環境下的執行確認
9. 常見問題(FAQ)
關於 Segmentation Fault,彙整了 C 語言學習者與現場工程師常提出的疑問與問題。若有符合的情況,歡迎參考。Q1. 要如何立即確定 Segmentation Fault 的原因?
A1. 首先確認錯誤訊息與發生的行號,了解是哪個處理程序發生問題。編譯時加上-g
選項,使用 gdb、Visual Studio 等除錯器執行,即可快速定位錯誤位置與堆疊追蹤。valgrind 等工具也非常有幫助。Q2. 若無法使用 gdb 或 valgrind 的環境,該如何調查?
A2. 使用printf
語句將變數值與處理進度細部顯示,掌握錯誤發生前的狀況。也可以利用 assert
檢查條件是否被破壞。若是複雜的 bug,根本調查較困難,理想情況是盡可能建置開發環境以使用相關工具。Q3. malloc 或 calloc 失敗會導致 Segmentation Fault 嗎?
A3. 當malloc
或 calloc
失敗時,返回值會是 NULL
。若直接參照 NULL
指標,就會產生 Segmentation Fault。務必在使用前確認記憶體是否成功分配(即非 NULL)。