段錯誤(Segmentation fault)
原文出處:http://oss.lzu.edu.cn/blog/article.php?tid_700.html
我只是把排版弄舒服一點,很好的文章,雖說是初級篇,但幫助確實很大。
1)往受到系統保護的內存地址寫數據
有些內存是內核佔用的或者是其餘程序正在使用,爲了保證系統正常工做,因此會受到系統的保護,而不能任意訪問.
#include <stdio.h>
int
main()
{
int i = 0;
scanf ("%d", i); /* should have used &i */
printf ("%d\n", i);
return 0;
}
編譯和執行一下, 咋一看,好像沒有問題哦,不就是讀取一個數據而後給輸出來嗎?
falcon@falcon:~/temp$ gcc -g -o segerr segerr.c –加-g選項查看調試信息
falcon@falcon:~/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 0×80483b7: 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存放一個值嗎?
[補充:
能夠經過man 7 signal查看SIGSEGV的信息。
falcon@falcon:~/temp$ man 7 signal | grep SEGV
Reformatting signal(7), please wait…
SIGSEGV 11 Core Invalid memory reference
例子2:
#include <stdio.h>
int
main()
{
char *p;
p = NULL;
*p = ‘x’;
printf(」%c」, *p);
return 0;
}
很容易發現,這個例子也是試圖往內存地址0處寫東西。
這裏咱們經過gdb來查看段錯誤所在的行
falcon@falcon:~/temp$ gcc -g -o segerr segerr.c
falcon@falcon:~/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.
0×08048516 in main () at segerr.c:10
10 *p = ‘x’;
(gdb)
2)內存越界(數組越界,變量類型不一致等)
#include <stdio.h>
int
main()
{
char test[1];
printf(」%c」, test[1000000000]);
return 0;
}
這裏是比較極端的例子,可是有時候多是會出現的,是個明顯的數組越界的問題
或者是這個地址是根本就不存在的
例子4:
#include <stdio.h>
int
main()
{
int b = 10;
printf(」%s\n」, b);
return 0;
}
咱們試圖把一個整數按照字符串的方式輸出出去,這是什麼問題呢?
因爲還不熟悉調試動態連接庫,因此
我只是找到了printf的源代碼的這裏
聲明部分:
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函數,麻煩幫忙找出越界的真正緣由吧,這個段錯誤也多是
處在va_start和va_arg等函數裏頭?或者直接看看這個這裏的printf源代碼的分析,看看是否
能夠找出出錯的地方:
http://www.wangchao.net.cn/bbsdetail_47325.html
相似的,還有諸如:sprintf等的格式控制問題
好比,試圖把char型或者是int的按照%s輸出或存放起來,如:
lor="black">#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型按照字符串轉換
3)其餘
其實大概的緣由都是同樣的,就是段錯誤的定義。
可是更多的容易出錯的地方就要本身不斷積累,不段發現,或者吸納前人已經積累的經驗,而且注意避免再次發生。
例如:
<1>定義了指針後記得初始化,在使用的時候記得判斷是否爲NULL
<2>在使用數組的時候是否被初始化,數組下標是否越界,數組元素是否存在等
<3>在變量處理的時候變量的格式控制是否合理等
一個比較不錯的例子:
我在進行一個多線程編程的例子裏頭,定義了一個線程數組
#define THREAD_MAX_NUM
pthread_t thread[THREAD_MAX_NUM];
用pthread_create建立了各個線程,而後用pthread_join來等待線程的結束
剛開始
我就直接等待,在建立線程都成功的時候,pthread_join可以順利等待各個線程結束
可是一旦建立線程失敗,那用pthread_join來等待那個本不存在的線程時天然會存在訪問不存在的內存的狀況,從而致使段錯誤的發生
後來
經過不斷調試和思考,而且獲得網絡上資料的幫助,找到了上面的出錯緣由和解決辦法
解決辦法是:
在建立線程以前,先初始化咱們的線程數組
在等待線程的結束的時候,判斷線程是否爲咱們的初始值
若是是的話,說明咱們的線程並無建立成功,因此就不能等拉。
上面給出了很常見的幾種出現段錯誤的地方,這樣在遇到它們的時候就容易避免拉。
可是人有時候確定也會有疏忽的,甚至可能仍是會常常出現上面的問題或者其餘常見的問題
因此對於一些大型一點的程序,如何跟蹤並找到程序中的段錯誤位置就是須要掌握的一門技巧拉。
4。如何發現程序中的段錯誤?
有個網友對這個作了比較全面的總結,除了感謝他外,我把地址弄了過來。
文章名字叫《段錯誤bug的調試》
地址是:http://www.cublog.cn/u/5251/showart.php?id=173718
應該說是很全面的。
而我經常使用的調試方法有:
1)在程序內部的關鍵部位輸出(printf)信息,那樣能夠跟蹤 段錯誤 在代碼中可能的位置
爲了方便使用這種調試方法,能夠用條件編譯指令#ifdef DEBUG和#endif把printf函數給包含起來,編譯的時候加上-DDEBUG參數就能夠查看調試信息。反之,不加上該參數進行調試就能夠。
2)用gdb來調試,在運行到段錯誤的地方,會自動停下來並顯示出錯的行和行號
這個應該是很經常使用的,若是須要用gdb調試,記得在編譯的時候加上-g參數,用來顯示調試信息
對於這個,網友在《段錯誤bug的調試》文章裏創造性的使用這樣的方法,使得咱們在執行程序的時候就能夠動態撲獲段錯誤可能出現的位置:
經過撲獲SIGSEGV信號來觸發系統調用gdb來輸出調試信息。
若是加上上面提到的條件編譯,那咱們就能夠很是方便的進行段錯誤的調試拉。
3)還有一個catchsegv命令 經過查看幫助信息,能夠看到