c語(yǔ)言中的各種指針(空指針、野指針甚颂、懸垂指針蜜猾、void指針)

首先聲明:這里說(shuō)的概念都是c語(yǔ)言中的,跟c++會(huì)有些許不一樣西设。

預(yù)備知識(shí)

1.空指針常量(null pointer constant)

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

這里即是說(shuō)明:值為0的整型常量表達(dá)式瓣铣,或強(qiáng)制(轉(zhuǎn)換)為 void * 類(lèi)型的此類(lèi)表達(dá)式,稱(chēng)為 空指針常量 贷揽。
如0棠笑、0L、3-3禽绪、'\0'蓖救、017、(void)0等都屬于空指針常量印屁。
至于系統(tǒng)會(huì)采用哪種形式來(lái)作為空指針常量使用循捺,則是和具體實(shí)現(xiàn)相關(guān)。一般的C系統(tǒng)采用 (void *)0 或者 0 的居多雄人,也有個(gè)別采用的 0L 从橘;至于C++系統(tǒng),由于存在嚴(yán)格的類(lèi)型轉(zhuǎn)換要求础钠, void * 不能像C中自由轉(zhuǎn)換成其他指針類(lèi)型恰力,所以通常選擇 0 作為空指針常量。

把空指針常量賦給指針類(lèi)型的變量p旗吁,p就成為了一個(gè)空指針踩萎。

2.NULL值

The macro NULL is defined in <stddef.h> (and other headers) as a null pointer constant.

即NULL是一個(gè)標(biāo)準(zhǔn)規(guī)定的宏定義,用來(lái)表示空指針常量很钓。
我們找到 stddef.h 中的該宏定義:

#define NULL ((void *)0)

毫無(wú)疑問(wèn)香府,NULL就是一種空指針常量董栽。
那有個(gè)問(wèn)題,我們可以自定義NULL值嗎企孩?
實(shí)際上NULL是標(biāo)準(zhǔn)庫(kù)中的一個(gè) reserved identifier (保留標(biāo)識(shí)符) 锭碳,所以如果包含了相應(yīng)的標(biāo)準(zhǔn)頭文件引入了NULL的話(huà),再在程序中重新定義NULL為其他值(比如把NULL定義為3)就是非法的柠硕。

一工禾、空指針(null pointer)

1.空指針定義

If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

通過(guò)預(yù)備知識(shí)中對(duì)于空指針常量NULL值的講解,我們可以知道:
只要將空指針常量賦給指針類(lèi)型變量,該指針變量就是空指針坯约。

int *p;
p = 0;
p = 0L;
p = '\0';
p = 3 - 3;
p = 0 * 17;
p = (void*)0;
p = NULL;

如上所示代碼蝌蹂,經(jīng)過(guò)其中任何一種賦值操作后,p就是一個(gè)空指針肥败。而且,由系統(tǒng)保證空指針不指向任何實(shí)際的對(duì)象或函數(shù)。反過(guò)來(lái)說(shuō)就是:任何對(duì)象或者函數(shù)的地址都不可能是空指針胁编。

2.空指針的內(nèi)存指向

標(biāo)準(zhǔn)并沒(méi)有對(duì)空指針指向內(nèi)存中什么地方這一問(wèn)題做出規(guī)定。也就是說(shuō)鳞尔,具體使用 0x0地址 還是其他地址來(lái)表示空指針嬉橙,都依賴(lài)于具體系統(tǒng)的實(shí)現(xiàn)。兩種實(shí)現(xiàn)如下:
(1)零空指針(zero null pointer)
這是我們常見(jiàn)的一種實(shí)現(xiàn)寥假,即空指針的內(nèi)部用全0來(lái)表示市框。
(2)非零空指針(nonzero null pointer)
也有一些系統(tǒng)用一些特殊的地址值或者特殊的方式來(lái)表示空指針。

我們?cè)趯?shí)際編程中不需要了解我們系統(tǒng)空指針的實(shí)現(xiàn)和內(nèi)存指向糕韧,我們只需要了解一個(gè)指針是否是空指針就可以了——編譯器會(huì)自動(dòng)實(shí)現(xiàn)其中的轉(zhuǎn)換枫振,為我們屏蔽掉其中的實(shí)現(xiàn)細(xì)節(jié)。

3.空指針的使用

空指針的使用萤彩,主要就是防止野指針和防止懸垂指針粪滤。
防止野指針

int *p = NULL;

防止懸垂指針

int *p = malloc(sizeof(int));
free(p);
p = NULL;  // 置空

詳細(xì)見(jiàn)下文野指針和懸垂指針。

二雀扶、野指針(wild pointer)

1.野指針概念

野指針:沒(méi)有初始化的指針

2.野指針產(chǎn)生原因

指針變量未初始化

如下程序:

int main()
{
    int *p;
    printf(%p", p);
    return 0;
}

這里的p未被初始化杖小,它的缺省值是隨機(jī)的。
因此我們?cè)诼暶饕粋€(gè)指針變量的時(shí)候愚墓,為了防止出現(xiàn)野指針的問(wèn)題予权,可以將其初始化為NULL,即設(shè)為空指針转绷;也可以咋初始化時(shí)就將指針確定指向伟件。
如下所示:

int a = 3;
int *p = &a;
// 或者
int *p = NULL;
int *p = 0;

其中int *p = 0和int *p = NULL都比較常用。

三议经、懸垂指針(dangling pointer)

1.懸垂指針概念

懸垂指針:指向已經(jīng)被釋放的自由區(qū)內(nèi)存(free store)的指針斧账。
它和野指針的區(qū)別就在于:懸垂指針曾經(jīng)有效過(guò)谴返,現(xiàn)在失效了;但是野指針從未有效過(guò)咧织。

2.懸垂指針產(chǎn)生原因

(1)指針指向的內(nèi)存釋放之后未置空

指針指向的內(nèi)存被free或者delete釋放后嗓袱,指針的值仍然為剛剛被釋放的那塊內(nèi)存的首地址,但是此時(shí)指針已經(jīng)失去了對(duì)那塊內(nèi)存的合法訪(fǎng)問(wèn)權(quán)限习绢。
如下程序所示:

int main()
{
    int *p = NULL;
    p = malloc(sizeof(int));
    *p = 3;
    printf("Before free, p = %p, *p = %d\n", p, *p);
    free(p);
    /* 注意渠抹,此時(shí)p和p指向的內(nèi)容并沒(méi)有發(fā)生變化,但是free內(nèi)存后已經(jīng)失去了
對(duì)堆上那塊內(nèi)存的合法操作性 */
    printf("After free, p = %p, *p = %d\n", p, *p);
    
    return 0;
}

程序輸出:

Before free, p = 0xe7a010, *p = 3
After free, p = 0xe7a010, *p = 0

在程序執(zhí)行free(p)之后闪萄,p就是一個(gè)野指針梧却。為了避免野指針可能引起的問(wèn)題,我們應(yīng)該在free(p)之后加上:

p = NULL;

這個(gè)操作就稱(chēng)為置空败去。

(2)指向同一塊內(nèi)存多個(gè)指針之一被釋放

這種情況嚴(yán)格來(lái)講跟第一種情況是一回事兒放航,示例代碼如下:

int *p = malloc(sizeof(int));
*p = 3;
int *pd = p;
/* 當(dāng)前p和pd指向的是同一塊內(nèi)存 */
free(p);
p = NULL;
/* 釋放掉p所指向的內(nèi)存,并將p置0 */

從上述代碼可以看出圆裕,若有兩個(gè)指針指向同一個(gè)內(nèi)存广鳍,其中一個(gè)指針被free且被置空后,另一個(gè)指針卻仍然指向那塊被釋放了的內(nèi)存空間吓妆,這就成了一個(gè)懸垂指針赊时。
這是懸垂指針產(chǎn)生的一個(gè)典型示例,常見(jiàn)于實(shí)際生產(chǎn)環(huán)境中行拢。

(3)指針操作超出變量生命周期

不要返回指向棧內(nèi)存的指針或引用祖秒,因?yàn)闂?nèi)存在函數(shù)結(jié)束時(shí)會(huì)被釋放。
示例代碼如下:

// 定義一個(gè)函數(shù)
char *getString()
{
    char *p = "Hello World!";
    return p;
}

在函數(shù)內(nèi)部定義的字符串指針p剂陡,其指向的內(nèi)容存放在棧上狈涮,當(dāng)這個(gè)函數(shù)執(zhí)行完后退出后,這部分空間就會(huì)被釋放鸭栖,返回過(guò)去的p指針就成了懸垂指針歌馍。

四、void指針(void pointer)

1.void指針概念

void的意思是“無(wú)類(lèi)型”晕鹊,所以void指針又被稱(chēng)為“無(wú)類(lèi)型指針”松却,void指針可以指向任何類(lèi)型的數(shù)據(jù),所以void指針一般被稱(chēng)為通用指針或者泛指針溅话,也被叫做萬(wàn)能指針晓锻。

2.void指針的使用

(1)void指針變量p所指向的內(nèi)容不能通過(guò)*p去訪(fǎng)問(wèn)

如果要通過(guò)void指針去獲取它所指向的變量值時(shí)候,需要先將void指針強(qiáng)制類(lèi)型轉(zhuǎn)換成和變量名類(lèi)型想匹配的數(shù)據(jù)類(lèi)型指針后再進(jìn)行操作飞几,如下所示:

int a = 5;
void *p = &a;
printf("*p=%d\n", *p);

編譯器會(huì)報(bào)錯(cuò)砚哆,提示你:

incomplete type is not allowed.

意思就是這個(gè)類(lèi)型不完整,沒(méi)有辦法去獲取它所指向的變量值屑墨。改成:

printf("*p = %d\n", *(int *)p);

就可以了躁锁。

(2)void指針賦給其他類(lèi)型的指針

一個(gè)常見(jiàn)的使用場(chǎng)景就是:動(dòng)態(tài)內(nèi)存申請(qǐng)與釋放纷铣。
首先要明確一件事,c語(yǔ)言和c++在一些語(yǔ)法實(shí)現(xiàn)上有區(qū)別战转,這里我們說(shuō)的是c語(yǔ)言搜立。

C語(yǔ)言中,void指針賦給其他任意類(lèi)型的指針(除開(kāi)函數(shù)指針槐秧,void指針賦給函數(shù)指針下面討論)啄踊,是天經(jīng)地義的,無(wú)需手動(dòng)強(qiáng)轉(zhuǎn)刁标;其他任意類(lèi)型的指針賦給void指針颠通,也是天經(jīng)地義的。

例如:

typedef struct {
    ...
    ...
} complex_struct;   
// c語(yǔ)言中的正確寫(xiě)法:
complex_struct = malloc(sizeof(complex_struct));
// c語(yǔ)言中多此一舉的寫(xiě)法:
// complex_struct = (complex_struct)malloc(sizeof(complex_struct));

如果你發(fā)現(xiàn)不手動(dòng)強(qiáng)轉(zhuǎn)命雀,編譯器出現(xiàn)warning蒜哀,請(qǐng)注意一下自己是不是忘了加malloc函數(shù)的頭文件:

#include <stdlib.h>

因?yàn)樵赾語(yǔ)言中,編譯器會(huì)把任何未定義的函數(shù)默認(rèn)返回int吏砂,因此如果我們不加stdlib.h這個(gè)頭文件,malloc函數(shù)默認(rèn)返回int乘客,所以我們看到編譯器警告狐血,如果看得不仔細(xì)的話(huà)會(huì)以為是因?yàn)闆](méi)有強(qiáng)制類(lèi)型轉(zhuǎn)換導(dǎo)致兩邊類(lèi)型不匹配,其實(shí)編譯器提示的是int *和int類(lèi)型不匹配易核,而不是int *和void *類(lèi)型不匹配匈织!
編譯器警告

(3)void指針賦給函數(shù)指針

void指針可以賦給函數(shù)指針以外的其他所有指針,malloc函數(shù)就是一個(gè)例子牡直。至于函數(shù)指針的問(wèn)題缀匕,這里來(lái)討論。
linux中有個(gè)dlsym函數(shù)碰逸,函數(shù)聲明如下:

void *dlsym(void *handle, const char *symbol);

man手冊(cè)中有個(gè)示例代碼:

int main()
{
    ...
    void *handle;
    double (*cosine)(double);
    ...
    *(void **) (&cosine) = dlsym(handle, "cos");
    ...
}

這里面它想用函數(shù)指針去接收dlsym函數(shù)返回的void指針乡小,卻沒(méi)有用以下兩種更自然的、好理解的形式:

cosine = dlsym(handle, "cos");
cosine = (double (*)(double)) dlsym(handle, "cos");

這是因?yàn)镃99標(biāo)準(zhǔn)的遺漏饵史,導(dǎo)致了void指針無(wú)法直接轉(zhuǎn)換成函數(shù)指針满钟。所以它用了下面這種夸張的轉(zhuǎn)換:

*(void **) (&cosine) = dlsym(handle, "cos");

先把consine取地址變成二級(jí)指針,然后將這個(gè)強(qiáng)轉(zhuǎn)成(void **)這個(gè)void二級(jí)指針胳喷,再經(jīng)過(guò)指針運(yùn)算符*變成void一級(jí)指針湃番,這樣左右兩邊類(lèi)型就匹配了。

(4)其他類(lèi)型的指針賦給void指針

void指針可以用作泛型吭露,接收任意數(shù)據(jù)類(lèi)型指針吠撮。
void指針用于指向特定地址,而無(wú)需關(guān)心這個(gè)地址上存放著什么類(lèi)型的數(shù)據(jù)讲竿。例如常見(jiàn)的memcpy等函數(shù)就用到void*泥兰,函數(shù)原型如下:

void *memcpy(void *des, void *src, size_t n);

此處的void *des和void *src可以接收任意類(lèi)型的數(shù)據(jù)類(lèi)型指針择浊,既然是內(nèi)存拷貝,入?yún)⒕筒粦?yīng)該限制傳入什么類(lèi)型的指針逾条,邏輯上十分合理琢岩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市师脂,隨后出現(xiàn)的幾起案子担孔,更是在濱河造成了極大的恐慌,老刑警劉巖吃警,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糕篇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡酌心,警方通過(guò)查閱死者的電腦和手機(jī)拌消,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)安券,“玉大人墩崩,你說(shuō)我怎么就攤上這事『蠲悖” “怎么了鹦筹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)址貌。 經(jīng)常有香客問(wèn)我铐拐,道長(zhǎng),這世上最難降的妖魔是什么练对? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任遍蟋,我火速辦了婚禮,結(jié)果婚禮上螟凭,老公的妹妹穿的比我還像新娘虚青。我一直安慰自己,他們只是感情好赂摆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布挟憔。 她就那樣靜靜地躺著,像睡著了一般烟号。 火紅的嫁衣襯著肌膚如雪绊谭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天汪拥,我揣著相機(jī)與錄音达传,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宪赶,可吹牛的內(nèi)容都是我干的宗弯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼搂妻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蒙保!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起欲主,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤邓厕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后扁瓢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體详恼,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年引几,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昧互。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伟桅,死狀恐怖敞掘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贿讹,我是刑警寧澤渐逃,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站民褂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疯潭。R本人自食惡果不足惜赊堪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望竖哩。 院中可真熱鬧哭廉,春花似錦、人聲如沸相叁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)增淹。三九已至椿访,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虑润,已是汗流浹背成玫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哭当。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓猪腕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钦勘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陋葡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容