在撰寫 C 語言程式處理文字檔案時,發現一個詭異的段錯誤。程式包含兩個主要函數 next() 與 state_init()。當輸入檔案較小(不足 1000 行)時運行正常,但一旦檔案行數達到 900 行左右,程式便在第二個 while 迴圈的 fgetc() 呼叫處觸發 Segment fault: core dumped。更令人困惑的是:使用 GDB 除錯時,有時又能正常通過,只有行數更多時 GDB 也會報錯。
void next(char c, int *row, int *col)
{
if (c == '\t')
*col = (*col + TABLEN - 1) / TABLEN * TABLEN + 1;
else if (c == '\n') {
cur_state.line_endpos[*row] = *col;
*col = 1;
(*row)++;
} else
(*col)++;
if (*col > cur_state.win_width) {
cur_state.line_endpos[*row] = *col - 1;
*col -= cur_state.win_width;
(*row)++;
}
}
void state_init()
{
char word;
int cur_row, cur_col;
cur_row = 1; cur_col = 1;
fseek(FP, 0, SEEK_SET);
ioctl(STDIN_FILENO, TIOCGWINSZ, &win);
cur_state.win_height = win.ws_row;
cur_state.win_width = win.ws_col;
while ((word = fgetc(FP)) != EOF)
next(word, &cur_row, &cur_col);
cur_state.total_line = cur_row - 1;
memset(&inbuffer, 0, sizeof(inbuffer));
}
cur_state.line_endpos 可能是固定大小陣列,當 *row 超過其容量時寫入越界,導致堆疊損壞。GDB 可能改變記憶體佈局,使錯誤延後或掩蓋。FP、cur_state 或 win 若未正確初始化,行為將不可預測。inbuffer 大小若不足,memset 可能破壞相鄰記憶體。fseek 後若檔案開啟模式不當,fgetc 可能讀取失敗。cur_state.line_endpos 的定義與最大行數限制,確保動態分配或足夠大小。next() 入口加入斷言:assert(*row < MAX_LINES);。valgrind 或 AddressSanitizer (-fsanitize=address) 偵測記憶體錯誤。word 宣告為 int 以正確比較 EOF。FP 以 "r" 或 "rb" 正確開啟,且 fseek 成功。段錯誤在大檔案才出現,通常指向資料結構容量不足或索引計算邏輯隨資料增長而越界。建議先從陣列邊界檢查著手,輔以記憶體檢測工具,即可定位根因。