在工作中遇到的問題實在是千奇百怪,跟C++搏鬥了好一陣子了,仍然還是跟它不是很熟,最近工作上更遇到了bug之王的segmentation fault錯誤,雖然還沒找出root cause,不過在找了一陣子未果後,想說透過更系統性的方法來調查甚麼原因會導致segmentation fault。
何謂 Segmentation fault (記憶體區段錯誤)?
記憶體區段錯誤,也稱存取權限衝突(access violation),它會出現在當程式企圖存取CPU無法定址的記憶體區段時。當錯誤發生時,硬體會通知作業系統產生了記憶體存取權限衝突的狀況。作業系統通常會產生核心轉儲(core dump)以方便開發者進行除錯。
因此,即便當下通過編譯,也有可能在執行程式的期間,由於記憶體位置存取不當而發生這類型的錯誤,錯誤訊息通常為 “Access Violation Writing Location"。
以下Grant整理了幾種會導致Segmentation fault 的情況。
(1)錯誤的訪問、寫入至唯讀記憶體
#include<stdio.h>
#include<stdlib.h>
int main() {
char *c = "hello world";
c[1] = 'H';
}
基本上當一個字元指標常量宣告並賦值後,就會被儲存於唯獨記憶體中,而試圖寫入至唯讀記憶體段會引發區段錯誤。因此,為了避免這種狀況,建議在常數前加上const,這樣在編譯階段,編譯器就會報錯了。
(2)訪問了不屬於程式地址空間的內存
指標在宣告後,必須指向對應的位址,但對於寫入受保護的位址,是不被允許的。
linux 位址上的頂部的四分之一是保留给內部核心的。即 0xffffffff 到 0xc0000000 是不可存取的地址,而下面的 0xc0000fff 即是在這區間。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)0xC0000fff;
*p = 10;
}
(3)寫入空指標
空指標(0 or NULL)時所指向的記憶體位址是受到保護的,所以一旦遭到強行寫入,都會發生區段錯誤。
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
int main(){
int *i=0;
std::cout << &i;
scanf ("%d", i);
printf ("%d\n", i);
}
(4)越界存取
再來一個常見引起區段錯誤的原因即是越界存取不存在的數組,例如以下的程式碼中,只宣告了長度為10的char,卻要印出c的位於第180000的資料。
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
int main() {
char c[10];
printf("%c", c[180000]);
}
以上就是一些關於Segmentation fault的知識,一開始對於這個錯誤毫無頭緒,再做過了一些調查也實際上 replicate issue 的 scenarios 之後,總算有點概念了,如果之後有找到工作上遇到的那個問題的root cause,Grant 會再來這裡更新一下。
C++在管理內存較其他語言來說,彈性相當高,這是個兩面刃,一旦對於內存不熟悉,就很有可能corrupt掉整個系統,因此在寫的時候要更加小心。
————–更新——————
後來發現的 root cause 是由於寫入不存在的記憶體。
舉例來說,就是程式中有個兩個陣列,有一個陣列裡面是存要使用的方法的位置,另一個是類似定義要讀檔的 enum,藉由 enum 陣列與方法陣列的 index 來 mapping對應的使用情境。
但這兩個陣列的的內容不匹配,也就是長度不同。例如第一個陣列長度 11,而第二個長度是 12,所以當程式今天讀到第 12 個位置的時候,沒有找到對應的方法的位址的定義,並且在寫入的時候噴錯,因為根本沒有辦法分配對應的位址給不存在的方法。不過,只要是讀1-11的定義都不會有事,也導致在特定情況時才會報出區段錯誤,解法就是把長度為 11 的陣列增加為 12 並分派對應的方法的 address。