2010-02-27 144 views
402

什麼是分段錯誤? C和C++有什麼不同?分段錯誤和懸掛指針如何相關?什麼是分段錯誤?

+55

段錯誤使[編譯器感覺不好](http://xkcd.com/371/)。 – 2011-09-12 14:32:19

+14

如果是這樣的話,爲什麼在我的情況下編譯器什麼也沒有抱怨,一切都很順利,但是在運行時系統會拋出一個分段錯誤(核心轉儲)? T_T – 2015-01-05 19:46:23

+2

只要內存轉儲出現問題! – resultsway 2015-04-25 01:18:58

回答

500

分段錯誤是由於訪問「不屬於你」的內存導致的一種特定類型的錯誤。它是幫助您避免損壞內存並引入難以調試的內存錯誤的輔助機制。每當你遇到段錯誤時,你都知道你在做內存錯誤 - 訪問已經釋放的變量,寫入內存的只讀部分等等。在大多數語言中,分段錯誤基本上是相同的,內存管理,C和C++中的段錯誤之間沒有主要區別。

有許多方法可以獲得段錯誤,至少在C(++)等低級語言中。得到段錯誤的一個常見方式是取消引用空指針:

char *str = "Foo"; // Compiler marks the constant string as read-only 
*str = 'b'; // Which means this is illegal and results in a segfault 

黨嶺:

int *p = NULL; 
*p = 1; 

另一個段錯誤當您嘗試寫入到被標記爲只讀存儲器的一部分發生指針指向一個東西不存在了,喜歡這裏:

char *p = NULL; 
{ 
    char c; 
    p = &c; 
} 
// Now p is dangling 

指針p懸,因爲它指向字符變量在塊結束後不再存在的。而當你試圖解引用懸掛指針(如*p='A')時,你可能會遇到段錯誤。

+115

最後一個例子特別討厭,當我構建時: int main() { char * p = 0; { char c ='x'; p =&c; } printf(「%c \ n」,* p); return 0; } 無論是gcc或其他幾個編譯器,它似乎工作。編譯時沒有警告。沒有段錯誤。這是因爲'}'超出範圍,並不實際刪除數據,只是將其標記爲可以再次使用。該代碼可以在生產系統上運行很多年,您可以更改代碼的另一部分,更改編譯器或其他內容,並且BOOOOOM! – 2010-04-13 09:06:16

+21

對不起,但只是一個側面說明...你的例子沒有一定會導致段錯誤,實際上它只是未定義的行爲;-) – oldrinb 2012-09-15 03:01:21

+10

@oldrinb:這是不可能編寫的代碼,*必然*導致段錯誤。不僅僅是因爲那裏有沒有內存保護的系統,因此不能分辨出一段內存實際上「屬於你」,因此**不知道**段錯誤,只有未定義的行爲......(經典的AmigaOS ,例如) – DevSolar 2014-05-29 18:03:48

25

分段錯誤是由於進程未在其描述符表中列出的頁面請求或其列出的頁面的無效請求導致的(例如,在只讀頁面上發出寫請求)。

一個懸掛指針是一個指針,它可能指向一個有效頁面,也可能不指向一個有效頁面,但會指向一個「意外」的內存段。

+6

這是真的,但如果你已經不知道分段錯誤是什麼,它會真的幫助你嗎? – zoul 2010-02-27 09:37:18

14

根據Wikipedia:

當 程序試圖訪問它不允許 存取存儲器 位置時,會發生段錯誤,或企圖的方式訪問存儲器 位置即不允許 (例如,試圖寫入到 只讀位置,或覆蓋 部分操作系統)。

21

說實話,正如其他海報所提到的,維基百科在這方面有一篇很好的文章so have a look there.這種類型的錯誤非常常見,並且經常被稱爲其他事情,例如訪問衝突或常規保護錯誤。

它們在C,C++或任何其他允許指針的語言中沒有區別。這些類型的錯誤通常是由那些

  1. 被正確初始化
  2. 他們指出,已經realloced或刪除內存後之前,使用指針引起的。
  3. 用於索引位於數組邊界之外的索引數組中。這通常只適用於在傳統數組或C字符串上進行指針計算,而不是基於STL/Boost的集合(使用C++)。
83

值得注意的是,段錯誤不是直接導致的訪問另一個進程內存(這是我有時聽到的),因爲它根本不可能。使用虛擬內存,每個進程都有自己的虛擬地址空間,並且無法使用任何指針值訪問另一個進程。例外是共享庫,這些共享庫映射到(可能)不同的虛擬地址和內核內存,它們在每個進程中都以相同的方式映射(爲了避免系統調用時TLB刷新,我認爲)。而像shmat這樣的東西) - 這些就是我所謂的「間接」訪問。但是,可以檢查它們通常位於遠離流程代碼的位置,我們通常可以訪問它們(這就是爲什麼它們在那裏,但以不恰當的方式訪問它們會產生分段錯誤)。

但是,如果以不正確的方式訪問我們自己的(進程)內存(例如嘗試寫入不可寫入的空間),則會發生段錯誤。但最常見的原因是對的虛擬地址空間部分的訪問沒有將映射到物理地址空間。

而這一切都與虛擬內存系統有關。

+0

有了共享內存/內存映射文件,別人可能會混淆你的內存。在WIN32中也有類似'WriteProcessMemory'的令人討厭的API! – paulm 2014-02-17 23:46:47

+1

@ paulm:是的,我知道。這就是我所想的「而像shmat這樣的東西;) - 這些就是我所謂的」間接「訪問。」 – 2014-02-18 10:08:18

+0

在虛擬內存操作系統中,無法訪問另一個進程虛擬內存的進程(通常,請操作系統實現者,請不要爲此阻止我),而不是某種內存連接系統調用,它允許您訪問。虛擬內存地址通常意味着不同的事情取決於正在考慮的過程。 – 2016-07-22 12:02:48

6

分段故障時的處理(運行的程序的實例)正試圖訪問發生只讀正在使用由其它過程或訪問不存在的(無效的)存儲器地址的存儲器地址或存儲器範圍。 懸空參考(指針)問題意味着試圖訪問一個對象或變量,其內容已被從存儲器中刪除,例如:

int *arr = new int[20]; 
delete arr; 
cout<<arr[1]; //dangling problem occurs here 
+4

刪除數組的正確方法是delete [] arr; – Damian 2016-03-28 15:48:06

9

分段故障也由硬件故障引起的,在這種情況下RAM存儲器。這是不太常見的原因,但是如果您在代碼中找不到錯誤,也許memtest可以幫助您。

在這種情況下的解決方案,更改RAM。

編輯:

這裏有一個參考:Segmentation fault by hardware

+1

有缺陷的RAM的快速和簡單的測試是在一個循環中反覆運行你的崩潰程序。如果程序沒有內部的不確定性 - 也就是說,它總是爲相同的輸入產生相同的輸出,或者至少它應該是這樣 - 但是對於某些特定的輸入,它會崩潰_sometimes_,並不總是但不是永遠不會:那麼你應該開始擔心壞RAM。 – zwol 2017-10-26 21:39:35

4

維基百科的頁面Segmentation_fault大約有它一個非常好的說明,只是指出了原因和理由。查看維基的詳細描述。

在計算中,分段錯誤(通常縮寫爲段錯誤)或訪問衝突是由具有內存保護的硬件引發的故障,通知操作系統(OS)關於內存訪問衝突。

以下是分段錯誤的一些常見原因:

  • 取消引用NULL指針 - 這是特例,通過內存管理硬件
  • 試圖訪問不存在的內存地址(外進程的地址空間)
  • 試圖訪問內存程序沒有權限(例如進程上下文中的內核結構)
  • 試圖寫入只讀內存(例如代碼段)

反過來,這些常常引起導致無效的內存訪問編程錯誤:解引用或分配到未初始化的指針(野生指針,它指向一個隨機存儲器地址)

  • 取消引用或分配給已釋放的指針(懸掛指針,它指向已釋放/釋放/刪除的內存)

  • 緩衝區溢出。

  • 堆棧溢出。

  • 試圖執行不能正確編譯的程序。 (一些編譯器將輸出一個可執行文件儘管編譯時錯誤的存在。)當程序試圖訪問是不存在的一個存儲器位置

0

段故障或訪問衝突發生時,或試圖訪問以不允許的方式存儲位置。

/* "Array out of bounds" error 
    valid indices for array foo 
    are 0, 1, ... 999 */ 
    int foo[1000]; 
    for (int i = 0; i <= 1000 ; i++) 
    foo[i] = i; 

這裏我[1000]不存在,所以出現段錯誤。

段故障的原因:

it arise primarily due to errors in use of pointers for virtual memory addressing, particularly illegal access. 

De-referencing NULL pointers – this is special-cased by memory management hardware. 

Attempting to access a nonexistent memory address (outside process’s address space). 

Attempting to access memory the program does not have rights to (such as kernel structures in process context). 

Attempting to write read-only memory (such as code segment). 
+2

首先,seg故障與地址確實或不存在無關。這是關於你正在訪問它,你不允許這樣做。在你的特殊例子中,它甚至可以保證這個位置存在。由於該標準在數組情況下說,必須給出在其邊界內的一個良好對齊數組上的指針pointg的有效地址**和**後面的1。 – dhein 2015-12-08 16:25:21

+0

它也與地址,如果你沒有地址,如果你嘗試訪問這個地址,也有seg。故障。在我的例子中,這僅僅是爲了理解的觀點。 – 2015-12-12 03:11:06

15

雖然Zoul的回答解釋了段錯誤是什麼,我發現,這些種蟲子可以特別拼命追趕,特別是如果你是新來的低像C++或C語言-level下面是一些常見的方式來獲得一個段錯誤在你的程序:

不當格式控制字符串中printfscanf聲明

格式控制字符串應該具有相同數量的轉換說明(%s%d等)作爲printfscanf具有參數要被打印或讀取。這同樣適用於fprintffscanf

不使用在參數&scanf

功能scanf採用作爲參數的格式控制字符串和變量將在其中放置它在讀取數據的地址。該&(地址)運算符用於提供變量的地址。

出界外數組引用

確保您沒有違反您正在使​​用的任何數組的邊界;即,您沒有爲數組下標數組,其值小於其最低元素的索引或大於其最高元素的索引。 Valgrind可以派上用場來檢測這些參考 - 您可以使用valgrind--tool=exp-sgcheck標誌。

訪問未初始化指針

指針變量必須被訪問之前被分配一個有效地址。確保你已經初始化所有的指針指向一個有效的內存區域。

使用不當&(地址)和*(解引用)運營商

您需要/使用這些的時候,尤其是在通過引用傳遞參數使用指針要小心。

shell限制

有時分割故障不是由程序中的錯誤引起的,但反而是造成系統內存限制被設置得太低。通常這是導致這種問題的堆棧大小的限制(堆棧溢出)。要檢查內存限制,請使用bash中的ulimit命令。

使用gdb

您可以使用調試器gdb查看core文件的程序傾倒的回溯調試。每當程序段錯誤時,他們通常會在崩潰時將內存內容轉儲到core文件(core dumped)中。使用-g標誌編譯程序,運行於gdb並使用bt(回溯)。

2

簡而言之:分段錯誤是操作系統發送信號給程序 ,說它檢測到非法的內存訪問並且過早地終止程序以防止內存被破壞。