1. 程式人生 > >Linux 程式設計段錯誤(segmentation error)總結

Linux 程式設計段錯誤(segmentation error)總結

最近一段時間在linux下用C做一些學習和開發,但是由於經驗不足,問題多多。而段錯誤就是讓我非常頭痛的一個問題。不過,目前寫一個一千行左右的程式碼,也很少出現段錯誤,或者是即使出現了,也很容易找出來,並且處理掉。

    那什麼是段錯誤?段錯誤為什麼是個麻煩事?以及怎麼發現程式中的段錯誤以及如何避免發生段錯誤呢?

    一方面為了給自己的學習做個總結,另一方面由於至今沒有找到一個比較全面介紹這個雖然是“particular problem”的問題,所以我來做個拋磚引玉吧。下面就從上面的幾個問題出發來探討一下“Segmentation faults"吧。

目錄

1。什麼是段錯誤?
2。為什麼段錯誤這麼“麻煩”?
3。程式設計中通常碰到段錯誤的地方有哪些?
4。如何發現程式中的段錯誤並處理掉?

正文

1。什麼是段錯誤?

下面是來自Answers.com的定義:
Quote:

A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors.

Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.

On Unix-like operating systems, a process that accesses invalid memory receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.

所謂的段錯誤就是指訪問的記憶體超出了系統所給這個程式的記憶體空間,通常這個值是由gdtr來儲存的,他是一個48位的暫存器,其中的32位是儲存由它指向的 gdt表,後13位儲存相應於gdt的下標,最後3位包括了程式是否在記憶體中以及程式的在cpu中的執行級別,指向的gdt是由以64位為一個單位的表,在這張表中就儲存著程式執行的程式碼段以及資料段的起始地址以及與此相應的段限和頁面交換還有程式執行級別還有記憶體粒度等等的資訊。一旦一個程式發生了越界訪問,cpu就會產生相應的異常保護,於是segmentation fault就出現了


通過上面的解釋,段錯誤應該就是訪問了不可訪問的記憶體,這個記憶體區要麼是不存在的,要麼是受到系統保護的。

2。為什麼段錯誤這麼麻煩?

寫程式好多年了,Segment fault 是許多C程式設計師頭疼的提示。指標是好東西,但是隨著指標的使用卻誕生了這個同樣威力巨大的惡魔。

Segment fault 之所以能夠流行於世,是與Glibc庫中基本所有的函式都預設型參指標為非空有著密切關係的。

不知道什麼時候才可以有能夠處理NULL的glibc庫誕生啊!

不得已,我現在為好多的函式做了衣服,避免glibc的函式被NULL給感染,導致我的Mem訪問錯誤,而我還不知道NULL這個病毒已經在侵蝕我的身體了。

Segment fault 永遠的痛......


    後面有好多網友都跟帖了,討論了Segmentation faults為什麼這麼“痛”,尤其是對於伺服器程式來說,是非常頭痛的,為了提高效率,要儘量減少一些不必要的段錯誤的“判斷和處理”,但是不檢查又可能會存在段錯誤的隱患。

    那麼如何處理這個“麻煩”呢?
    就像人不可能“完美”一樣,由人創造的“計算機語言“同樣沒有“完美”的解決辦法。
    我們更好的解決辦法也許是:

    通過學習前人的經驗和開發的工具,不斷的嘗試和研究,找出更恰當的方法來避免、發現並處理它。對於一些常見的地方,我們可以避免,對於一些“隱藏”的地方,我們要發現它,發現以後就要及時處理,避免留下隱患。

    下面我們可以通過具體的實驗來舉出一些經常出現段錯誤的地方,然後再舉例子來發現和找出這類錯誤藏身之處,最後處理掉。

3。程式設計中通常碰到段錯誤的地方有哪些?

為了進行下面的實驗,我們需要準備兩個工具,一個是gcc,一個是gdb
我是在ubuntu下做的實驗,安裝這兩個東西是比較簡單的
Quote:

sudo apt-get install gcc-4.0 libc6-dev
sudo apt-get install gdb

好了,開始進入我們的實驗,我們粗略的分一下類

1)往受到系統保護的記憶體地址寫資料

    有些記憶體是核心佔用的或者是其他程式正在使用,為了保證系統正常工作,所以會受到系統的保護,而不能任意訪問。

例子1:

Code:
#include <stdio.h>intmain(){        int i = 0;        scanf ("%d", i); /* should have used &i */        printf ("%d/n", i);        return 0;}
[Ctrl+A Select All]


    編譯和執行一下
Quote:

    咋一看,好像沒有問題哦,不就是讀取一個數據然後給輸出來嗎?

下面我們來除錯一下,看看是什麼原因?
Quote:

[email protected]:~/temp$ gcc -g -o segerr segerr.c        --加-g選項檢視除錯資訊
[email protected]:~/temp$ gdb ./segerr
GNU gdb 6.4-debian
Copyright 2005 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/ lib/tls/i686/cmov/libthread_db.so.1".

(gdb) l                    --用l(list)顯示我們的原始碼
1       #include <stdio.h>
2
3       int
4       main()
5       {
6               int i = 0;
7
8               scanf ("%d", i); /* should have used &i */
9               printf ("%d/n", i);
10              return 0;
(gdb) b 8                --用b(break)設定斷點
Breakpoint 1 at 0x80483b7: file segerr.c, line 8.
(gdb) p i                --用p(print)列印變數i的值[看到沒,這裡i的值是0哦]
$1 = 0

(gdb) r                    --用r(run)執行,直到斷點處
Starting program: /home/falcon/temp/segerr

Breakpoint 1, main () at segerr.c:8
8               scanf ("%d", i); /* should have used &i */ --[試圖往地址0處寫進一個值]
(gdb) n                    --用n(next)執行下一步
10

Program received signal SIGSEGV, Segmentation fault.
0xb7e9a1ca in _IO_vfscanf () from /lib/tls/i686/cmov/libc.so.6
(gdb) c            --在上面我們接收到了SIGSEGV,然後用c(continue)繼續執行
Continuing.

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) quit        --退出gdb


果然
我們“不小心”把&i寫成了i
而我們剛開始初始化了i為0,這樣我們不是試圖向記憶體地址0存放一個值嗎?實際上很多情況下,你即使沒有初始化為零,預設也可能是0,所以要特別注意。

補充:
可以通過man 7 signal檢視SIGSEGV的資訊。
Quote:

[email protected]:~/temp$ man 7 signal | grep SEGV
Reformatting signal(7), please wait...
       SIGSEGV      11       Core    Invalid memory reference


例子2:

Code:
#include <stdio.h>intmain(){        char *p;        p = NULL;        *p = 'x';        printf("%c", *p);        return 0;}
[Ctrl+A Select All]


很容易發現,這個例子也是試圖往記憶體地址0處寫東西。

這裡我們通過gdb來檢視段錯誤所在的行
Quote:

[email protected]:~/temp$ gcc -g -o segerr segerr.c
[email protected]:~/temp$ gdb ./segerr
GNU gdb 6.4-debian
Copyright 2005 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) r        --直接執行,我們看到丟擲段錯誤以後,自動顯示出了出現段錯誤的行,這就是一個找出段錯誤的方法
Starting program: /home/falcon/temp/segerr

Program received signal SIGSEGV, Segmentation fault.
0x08048516 in main () at segerr.c:10
10              *p = 'x';
(gdb)


2)記憶體越界(陣列越界,變數型別不一致等)

例子3:


Code:
#include <stdio.h>intmain(){        char test[1];        printf("%c", test[1000000000]);        return 0;}
[Ctrl+A Select All]


這裡是比較極端的例子,但是有時候可能是會出現的,是個明顯的陣列越界的問題
或者是這個地址是根本就不存在的

例子4:


Code:
#include <stdio.h>intmain(){        int b = 10;        printf("%s/n", b);        return 0;}
[Ctrl+A Select All]


我們試圖把一個整數按照字串的方式輸出出去,這是什麼問題呢?
由於還不熟悉除錯動態連結庫,所以
我只是找到了printf的原始碼的這裡
Quote:

宣告部分:
    int pos =0 ,cnt_printed_chars =0 ,i ;
unsigned char *chptr ;
va_list ap ;
%s格式控制部分:
case 's':
    chptr =va_arg (ap ,unsigned char *);
    i =0 ;
    while (chptr [i ])
    {...
        cnt_printed_chars ++;
        putchar (chptr [i ++]);
}

仔細看看,發現了這樣一個問題,在列印字串的時候,實際上是列印某個地址開始的所有字元,但是當你想把整數當字串列印的時候,這個整數被當成了一個地址,然後printf從這個地址開始去列印字元,指導某個位置上的值為/0。所以,如果這個整數代表的地址不存在或者不可訪問,自然也是訪問了不該訪問的記憶體——segmentation fault。

類似的,還有諸如:sprintf等的格式控制問題
比如,試圖把char型或者是int的按照%s輸出或存放起來,如:


Code:
#include <stdio.h>#include <string.h>char c='c';int i=10;char buf[100];printf("%s", c);        //試圖把char型按照字串格式輸出,這裡的字元會解釋成整數,再解釋成地址,所以原因同上面那個例子printf("%s", i);            //試圖把int型按照字串輸出memset(buf, 0, 100);sprintf(buf, "%s", c);    //試圖把char型按照字串格式轉換memset(buf, 0, 100);sprintf(buf, "%s", i);   //試圖把int型按照字串轉換
[Ctrl+A Select All]

3)其他

其實大概的原因都是一樣的,就是段錯誤的定義。但是更多的容易出錯的地方就要自己不斷積累,不段發現,或者吸納前人已經積累的經驗,並且注意避免再次發生。

例如:

<1>定義了指標後記得初始化,在使用的時候記得判斷是否為NULL
<2>在使用陣列的時候是否被初始化,陣列下標是否越界,陣列元素是否存在等
<3>在變數處理的時候變數的格式控制是否合理等

再舉一個比較不錯的例子:

我在進行一個多執行緒程式設計的例子裡頭,定義了一個執行緒陣列
#define THREAD_MAX_NUM
pthread_t thread[THREAD_MAX_NUM];
用pthread_create建立了各個執行緒,然後用pthread_join來等待執行緒的結束

剛開始我就直接等待,在建立執行緒都成功的時候,pthread_join能夠順利等待各個執行緒結束,但是一旦建立執行緒失敗,那用pthread_join來等待那個本不存在的執行緒時自然會存在訪問不能訪問的記憶體的情況,從而導致段錯誤的發生,後來,通過不斷除錯和思考,並且得到網路上資料的幫助,找到了上面的原因和解決辦法:

在建立執行緒之前,先初始化我們的執行緒陣列,在等待執行緒的結束的時候,判斷執行緒是否為我們的初始值
如果是的話,說明我們的執行緒並沒有建立成功,所以就不能等拉。否則就會存在釋放那些並不存在或者不可訪問的記憶體空間。

上面給出了很常見的幾種出現段錯誤的地方,這樣在遇到它們的時候就容易避免拉。但是人有時候肯定也會有疏忽的,甚至可能還是會經常出現上面的問題或者其他常見的問題,所以對於一些大型一點的程式,如何跟蹤並找到程式中的段錯誤位置就是需要掌握的一門技巧拉。

4。如何發現程式中的段錯誤?

而我常用的除錯方法有:

1)在程式內部的關鍵部位輸出(printf)資訊,那樣可以跟蹤 段錯誤 在程式碼中可能的位置

為了方便使用這種除錯方法,可以用條件編譯指令#ifdef DEBUG和#endif把printf函式給包含起來,編譯的時候加上-DDEBUG引數就可以檢視除錯資訊。反之,不加上該引數進行除錯就可以。

2)用gdb來除錯,在執行到段錯誤的地方,會自動停下來並顯示出錯的行和行號

這個應該是很常用的,如果需要用gdb除錯,記得在編譯的時候加上-g引數,用來顯示除錯資訊,對於這個,網友在《段錯誤bug的除錯》文章裡創造性的使用這樣的方法,使得我們在執行程式的時候就可以動態撲獲段錯誤可能出現的位置:通過撲獲SIGSEGV訊號來觸發系統呼叫gdb來輸出除錯資訊。如果加上上面提到的條件編譯,那我們就可以非常方便的進行段錯誤的除錯拉。

3)還有一個catchsegv命令
通過檢視幫助資訊,可以看到
Quote:

Catch segmentation faults in programs

這個東西就是用來撲獲段錯誤的,不過打印出來的是一些register裡頭的東西,“看不太懂”。

到這裡,“初級總結篇”算是差不多完成拉。歡迎指出其中表達不當甚至錯誤的地方,先謝過!


參考資料[具體地址在上面的文章中都已經給出拉]:

後記

雖然感覺沒有寫什麼東西,但是包括查詢資料和打字,也花了好些幾個小時,不過總結一下也是值得的,歡迎和我一起交流和討論,也歡迎對文章中表達不當甚至是錯誤的地方指正一下 

相關推薦

Linux 程式設計錯誤segmentation error總結

最近一段時間在linux下用C做一些學習和開發,但是由於經驗不足,問題多多。而段錯誤就是讓我非常頭痛的一個問題。不過,目前寫一個一千行左右的程式碼,也很少出現段錯誤,或者是即使出現了,也很容易找出來,並且處理掉。     那什麼是段錯誤?段錯誤為什麼是個麻煩事?以及怎麼發現程

Linux下使用-static -lpthread靜態編譯出現錯誤Segmentation fault

最近在看golang,感覺go的靜態編譯思想很不錯。於是準備把手頭的幾個專案靜態編譯一下,結果編譯的時候沒報任何警告及錯誤,一執行就報段錯誤(Segmentation fault)。gdb逐步除錯,發現問題出在std::thread那裡。仔細檢查了下所有語法,沒

Linux下的錯誤Segmentation fault產生的原因及除錯方法

 段錯誤 就是訪問了錯誤的記憶體段,一般是你沒有許可權,或者根本就不存在對應的實體記憶體,尤其常見的是訪問0地址.         一 般來說,段錯誤就是指訪問的記憶體超出了系統所給這個程式的記憶體空間,通常這個值是由gdtr來儲存的是一個48位的暫存器,              前32位是儲存由它指

C/C++中的錯誤Segmentation fault

}3)其他其實大概的原因都是一樣的,就是段錯誤的定義。但是更多的容易出錯的地方就要自己不斷積累,不段發現,或者吸納前人已經積累的經驗,並且注意避免再次發生。例如:<1>定義了指標後記得初始化,在使用的時候記得判斷是否為NULL<2>在使用陣列的時候是否被初始化,陣列下標是否越界,陣列元

你的java/c/c++程式崩潰了?揭祕錯誤Segmentation fault3

前言 接上兩篇: 寫到這裡,越跟,越發現真的是核心上很白,非一般的白。 但是既然是研究,就定住心,把段錯誤搞到清楚明白。 本篇將作為終篇,來結束這個系列,也算是對段錯誤和程式除錯、尋找崩潰原因(通常不會給你那麼完美的stackstrace和人性化的錯

嵌入式 使用gdb除錯錯誤segment fault

我們打算使用gdb去解決為什麼下面的程式(檔案為segfault.c)引起了段錯誤的問題。下面的這段程式是從使用者那裡讀入一行文字字串然後顯示在螢幕上。然而,如下當前的程式並不會如期執行... [cpp] view plaincopyprint? <s

linux中gdb的使用 Linux程式設計基礎——GDB設定斷點

斷點   在程式碼的指定位置中斷,使程式在此中斷。 break <function>    在進入指定函式時停住 break <linenum>    

使用gdb除錯錯誤segment fault

我們打算使用gdb去解決為什麼下面的程式(檔案為segfault.c)引起了段錯誤的問題。下面的這段程式是從使用者那裡讀入一行文字字串然後顯示在螢幕上。然而,如下當前的程式並不會如期執行... #include <stdio.h> #include <

錯誤核心已轉儲問題的分析方法(未成功)

是否產生core dump ulimit -c 0 #unlimited太大,沒有必要。 ulimit -c 10000 ulimit -c unlimited 用上面命令只會對當前的終端環境有效.如果想需要永久生效,修改檔案 /etc/security/limit

Linux初級運維十五——bash指令碼程式設計之函式

一、函式         函式:功能,function  程式碼重用的功能。         結構化程式設計,不能獨立執行,需要呼叫

Linux運維必會的實戰程式設計筆試題19題

以下Linux運維筆試面試程式設計題,彙總整理自老男孩、馬哥等培訓機構,由運維派根據實戰需求,略有調整: 企業面試題1:(生產實戰案例):監控MySQL主從同步是否異常,如果異常,則傳送簡訊或者郵件給管理員。提示:如果沒主從同步環境,可以用下面文字放到檔案裡讀取來模擬: 階段1:開發一個守護程序指令

從新手到系統管理員Linux Shell指令碼程式設計之數學Part I

本文由 [茶話匯] – [Qing] 編譯自 [Avishek Kumar] 轉載請註明出處 這部分主要討論數學相關的shell指令碼程式設計。 加法運算 新建一個檔案“Addition.sh”,輸入下面的內容並賦予其可執行的許可權。 [code language=”bash”] #!/bin/b

C語言 --錯誤核心已轉儲

文章原地址:https://blog.csdn.net/qq_29350001/article/details/53780697 (侵刪欠)一、什麼是段錯誤?一旦一個程式發生了越界訪問,cpu 就會產生相應的保護,於是 segmentation fault 就出現了,通過上面

VMware不可恢復錯誤disk error while paging真的不能恢復麼?

一大早啟動虛機就出現問題了,沒遇到過,鬱悶中。。。 人家提示都說,不可恢復錯誤,暈眩中。。。 可是不甘心就這麼算了,死馬當活馬醫吧,折騰起來。。。 以前遇到過“VMware非正常關閉,導致無法獲取虛擬機器所有權的解決辦法”,嘗試,失敗。。。繼續想辦法,刪除了與自己認為無關的

C語言再學習 -- 錯誤核心已轉儲

一、什麼是段錯誤?一旦一個程式發生了越界訪問,cpu 就會產生相應的保護,於是 segmentation fault 就出現了,通過上面的解釋,段錯誤應該就是訪問了不可訪問的記憶體,這個記憶體區要麼是不存在的,要麼是受到系統保護的,還有可能是缺少檔案或者檔案損壞。二、段錯誤產

linux程式設計實踐4實現ls -l命令

下面是ls -l的簡單實現,還有很多不完善的地方,如沒有進行排序等。 #include<stdio.h> #include<dirent.h> #include<sys/types.h> #include<sys/stat.h&

Unix/Linux程式設計實踐教程

-------------------------------前言呃。。。。開始入坑linux。參考書當然是《Unix/Linux程式設計實踐教程》現寫下心得筆記,有什麼不對的,請大家指正哈。------------------------------修正2018.5.13貌

linux程式設計中遇到的Segmentation fault錯誤

昨天程式設計的時候,遇到了Segmentation fault錯誤。使用GDB除錯時才發現的,順便說下。習慣了圖形介面式的除錯工具,用命令列確實還是不習慣。關於此錯誤問了下谷歌,一搜一大篇,看來是一個

程式設計錯誤長久更新

1.01:【線段樹】左右兒子結點編號傳入錯誤——2017.08.15 1.02:【線段樹】儲存資料未選擇合適儲存型別儲存(爆int型)——2017.08.16 1.03:【線段樹】未判斷輸入資料是否合法(是否存在L > R的情況)——2017.08.1

linux網路程式設計之socket十六:通過UNIX域套接字傳遞描述符和 sendmsg/recvmsg 函式

void send_fd(int sock_fd, int send_fd) {     int ret;     struct msghdr msg;     struct cmsghdr *p_cmsg;     struct iovec vec;     char cmsgbuf[CMSG_SPACE(