你的C/C++程式為什麼無法執行?揭祕Segmentation fault (1)
什麼讓你對C/C++如此恐懼?
晦澀的語法?還是優秀IDE的欠缺?
我想那都不是問題,最多的可能是一個類似這樣的錯誤:
段錯誤(Segmentation fault)
這是新手無法避免的錯誤,也是老手極力迴避也經常遇到的錯誤。
本篇,試圖簡略地剖析一段會引發這個錯誤的程式,帶來一些啟發。
先看兩份程式碼,一份是錯誤的.
錯誤程式碼
#include "string.h"
#include <stdlib.h>
#include <stdio.h>
void func1(char ** dest,char * src,int n) {
(*dest) = (char *)malloc(sizeof(char)*n);
strcpy(*dest,src);
}
int main(int argc,char** args) {
char ** p = NULL;
char str[] = "foreach_break";
int len = sizeof(str);
printf("%d\n",len);
func1(p,str,len);
printf("%s\n",*p);
free(p);
p = NULL;
}
正確程式碼
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
void func1(char ** dest,char * src,int n) {
(*dest) = (char*)malloc(sizeof(char)*n);
strcpy(*dest,src);
}
int main(int argc,char** args) {
char * p = NULL;
char str[] = "foreach_break";
int len = sizeof(str);
printf("%d\n",len);
func1(&p,str,len);
printf ("%s\n",p);
free(p);
//p = NULL;
}
程式碼意圖來自技術問答中的一個huffman樹不能執行的問題。
當然,我剝離掉了大部分關於huffman的部分,並稍加改動。
它們最大的不同:
錯誤程式碼:
char ** p = NULL;
func1(p,str,len);
正確程式碼:
char * p = NULL;
func1(&p,str,len);
也許你會奇怪,你看到“正確”的程式碼中居然註釋了這行:
//p = NULL;
引數傳遞
同時,可能有人會覺得指標char ** p
向函式func1傳遞*p
,與指標char * p
向函式func1
傳遞&p
沒什麼不同啊?
嗯。這種想法也很有慣性,因為C語言沒有類似這樣的函式宣告:
void func(int &)
同時,對char * p
作&p
操作不也得到個char **
嗎?
執行程式
那麼,我們看看程式自己怎麼說?
如果你經常遇到段錯誤,希望你仔細看明白上面的圖在說什麼。
野指標
所謂野指標,就是很野的指標,你不知道它指向了哪個地址,也不知道對這個地址取值是否會出錯,但,野指標也是指標,有一個存放它的記憶體地址.
正確的程式碼中,存放指標
p
的地址是0x7fffffffddc0
;
錯誤的程式碼中,存放指標p
的地址是0x7fffffffdd78
;
零指標
所謂零指標,就是指向了0x0的指標.對這個0x0取值是否會出錯呢?你想一想.
懸浮指標
你對於在正確的程式中註釋了p = NULL
而感到不解?
其實這沒什麼,取決於這個指標p
在後續程式碼中怎麼使用.
free(p);
這句程式碼的執行,會釋放掉指標p
所指向的記憶體地址,歸還給作業系統.
當然前提是這個地址確有所指、你也有權訪問它.
p = NULL;
這句程式碼的執行,是讓指標p
指向了0x0
,變回了空指標.
雖然它指向的記憶體已經被釋放,但是它還指向那個地址.
這就是懸浮指標,指向的地址已經不可用確還指向,就不是確有所指.
由於我們的main
函式即將執行完畢,所以在它返回後,存放空指標p
的地址會被釋放.
因為指標
p
是main
函式的一個臨時變數.
所以我們可以毫無顧慮的註釋掉p = NULL
.
記憶體洩露
另外,如果free(p)
沒有被執行,而先執行了p = NULL
,那麼p
原來指向的記憶體空間可能就無法被正確釋放,如果再也沒有其它的引用指向了那塊地址,那塊地址就被遺忘在那裡,同時不能被回收.
這個,叫記憶體洩露 (Memory Leak).
接著看程式
現在,我們來看看函式func1
的呼叫。
首先,是C程式碼:
然後是兩段程式碼的對比:
你應該已經看出了差別。
錯誤的程式碼的dest
引數傳入了0x0
.
接著是執行:
(*dest) = (char*)malloc(sizeof(char)*n);
這句程式碼在進行(*dest)
時就會發生段錯誤.
究其原因,就在於char ** p = NULL
讓p
變成了零指標,*p
相當於對0x0
這個地址取值.
應用程式啟動時,作業系統會建立一個程序(process),這個程序擁有自己獨立的地址空間,稱作虛擬地址空間(virtual memory space).
0x0
在這個空間中,不能被訪問.
試圖訪問一個不能被訪問的空間,就會段錯誤.
總結
段錯誤的一種,我們探索完畢.
現在你知道以下兩種操作的含義了嗎?
/* p指向的地址,對其取值,如果這個地址有東西,也有權取,沒問題.*/
char ** p -> *p; (1)
/* 存放p的地址,或者指向p的引用,這個地址必然有東西,所以沒問題*/
char * p -> &p (2);
本篇結束.