c語言中指針與數(shù)組的異同分析

一惕虑、背景


先來個問題,下面的一段代碼編譯時會存在問題嗎睛挚?

#include <stdio.h>
#include <string.h>

int main(void)
{
    unsigned char a[2];
    
    bzero(a, sizeof(a));
    
    if (a) {
        printf("a is valid!\n");
    }
    
    return 0;
}

很明顯邪蛔,這個程序是沒有問題的,至少語法上沒有問題扎狱。但是使用

gcc -Wall

選項編譯時侧到,會出現(xiàn)如下的編譯警告:

warning: the address of ‘a(chǎn)’ will always evaluate as ‘true’ [-Waddress]

自然,判斷一個數(shù)組是否為真是多余的淤击,因為它確定無疑是真的匠抗!另一方面,把判斷條件換為

if (a != NULL)

時污抬,是不會產(chǎn)生編譯警告的汞贸。就是這樣一個在語法上沒有問題的小程序,引起了我的興趣印机。我想就此一探指針與數(shù)組之間的微妙關系著蛙。

二、數(shù)組不是指針


1. 先上定義

數(shù)組耳贬,用來存儲一個固定大小的相同類型元素的順序集合。在c語言中猎唁,數(shù)組屬于構(gòu)造數(shù)據(jù)類型咒劲。

有限個類型相同的變量的集合命名為數(shù)組名。組成數(shù)組的各個變量稱為數(shù)組的分量诫隅,也稱為數(shù)組的元素或下標變量腐魂。用于區(qū)分數(shù)組的各個元素的數(shù)字編號稱為下標。

數(shù)組是用于儲存多個相同類型數(shù)據(jù)的集合逐纬。

指針是一種保存變量地址的變量蛔屹。

指針(Pointer)的值直接指向(points to)存在存儲器中另一個地方的值。由于通過地址能找到所需的變量單元豁生,可以說兔毒,地址指向該變量單元漫贞。

2. 定義與聲明的區(qū)分

經(jīng)常會遇到下面一段程序代碼的情況:

/* 文件 1 */
int a[10];

/* 文件 2 */
extern int *a;
/* 下面是開始使用a[i]的代碼 */

很多人認為這是沒有問題的,畢竟育叁,在c語言中數(shù)組與指針非常相似迅脐,甚至可以互換。那么豪嗽,把定義為數(shù)組的變量聲明為指針使用有什么問題呢谴蔑?

對于下面的這段代碼,沒有人懷疑它是錯誤的:

/* 文件 1 */
int i;

/* 文件 2 */
extern float i;
/* 下面是開始使用i的代碼 */

對于定義與聲明類型不匹配的情況龟梦,沒人指望它能正常運行隐锭。但是數(shù)組與指針的類型也不相同啊,為什么還要指望它能正常運行呢计贰?

c語言中的變量必須有且只有一個定義钦睡,但它可以有多個extern聲明,定義創(chuàng)建了一個對象蹦玫,并為其分配了內(nèi)存空間赎婚。而聲明只是簡單地說明了在其他地方創(chuàng)建的對象的名字,它允許你使用這個名字而已樱溉。

編譯器為每個變量分配一個地址挣输,地址編譯時可知,并且該變量在運行時一直保存在這個地址福贞。相反撩嚼,存儲在變量中的值只有在運行時才可知。當需要從變量中存儲的值時挖帘,編譯器從指定地址讀出變量的值在于寄存器完丽。

對于數(shù)組,編譯器可以直接對其操作拇舀,不需要增加指令首先取得具體的地址逻族。對于指針,必須首先在運行時取得它的當前值骄崩,然后才能對它進行解除引用操作聘鳞。

所以,extern char a[]extern char a[10]等價要拂。編譯器并不需要知道數(shù)組具體長度抠璃,它只產(chǎn)生偏離起始地址的偏移地址。從數(shù)組中取一個字符脱惰,只要簡單地從符號表顯示的a的地址加上下標搏嗡,需要的字符就在這個地址中。

而聲明為 extern char *p,編譯器認為p是一個指針采盒,它指向的對象是一個字符旧乞,為了取到這個字符,要先取得地址p的內(nèi)容纽甘,然后把它作為字符的地址良蛮,從而取出字符。

可見悍赢,指針的訪問比數(shù)組增加一次額外的提取决瞳。

同樣的道理,下面的使用會導致什么后果呢左权?

/* 文件 1 */
int *a;

/* 文件 2 */
extern int a[];
/* 下面是開始使用a的代碼 */

外部數(shù)組實際定義為指針皮胡,但作為數(shù)組使用,需要對內(nèi)存進行直接的引用赏迟,但這里編譯器所執(zhí)行的卻是對內(nèi)存進行間接引用屡贺。因為編譯器認為它是一個指針,而非數(shù)組锌杀。

再回過頭來看一下這個問題甩栈,p聲明為 extern char *p,而它原本的定義是'char p[10]'時糕再,當用p[i]使用p時量没,實際上得到的是一個字符,但編譯器會把它當成一個指針突想。把ASCII碼解釋成地址顯然很荒謬殴蹄,這時程序崩潰你應該感到很高興,不然猾担,這個bug可能會破壞程序地址空間的內(nèi)容袭灯,出現(xiàn)莫名其妙的錯誤。

3. 數(shù)組和指針的特性對比表

數(shù)組 指針
保存數(shù)據(jù) 保存數(shù)據(jù)的地址
直接訪問數(shù)據(jù) 間接訪問數(shù)據(jù)绑嘹,先取出指針的內(nèi)容稽荧,把它作為地址,然后從該地址取出數(shù)據(jù)
通常用于存儲固定數(shù)目且類型相同的元素 通常用于動態(tài)數(shù)據(jù)結(jié)構(gòu)
隱式分配和刪除 使用malloc()和free()
自身為數(shù)據(jù)名 通常指向匿名數(shù)據(jù)

4. 字符串常量

指針和數(shù)組的另一個區(qū)別是定義字符串常量時工腋。使用指針定義一個字符串常量姨丈,如:

char *p = "abcdefg";

此時,編譯器會為字符串常量分配內(nèi)存夷蚊。注意,只有字符串常量才是如此髓介,不會為浮點數(shù)之類的常量分配空間惕鼓。在ANSI C中,初始化指針所創(chuàng)建的字符串常量被定義為只讀唐础,如果試圖通過指針修改這個字符串常量的值箱歧,就會出現(xiàn)未定義的錯誤矾飞。

使用數(shù)組定義字符串常量時,如:

char a[] = "abcdefg";

字符串的值是可以修改的呀邢。

三洒沦、數(shù)組是指針


1. 數(shù)組是指針的情況

其實,在實際的應用中价淌,數(shù)組和指針可以等同的情況要比它們不能互換的場景多得多申眼。例如,牢記以下準則:

所有作為函數(shù)參數(shù)的數(shù)組名總是可以通過編譯器轉(zhuǎn)換為指針蝉衣。

而在任何其他情況下括尸,數(shù)組的聲明就是數(shù)組,指針的聲明就是指針病毡,兩者不可互換濒翻。但在使用數(shù)組時,數(shù)組總是可以寫成指針的形式啦膜,兩者可以互換有送。

數(shù)組與指針相同的情況總結(jié)如下:

  • 表達式中的數(shù)組名被編譯器當作一個指向該數(shù)組第一個元素的指針
  • 下標總是與指針的偏移量相同
  • 在函數(shù)參數(shù)的聲明中,數(shù)組名被編譯器當作指向該數(shù)組第一個元素的指針

2. 為什么把數(shù)組當成指針

基于以下原因僧家,c語言把數(shù)組形參當作指針使用:

  • 把傳遞給函數(shù)的數(shù)組參數(shù)轉(zhuǎn)換為指針是由于效率的考慮
  • 把作為形參的數(shù)組和指針等同起來也是出于效率的原因雀摘。c語言中,所有非數(shù)組形式的實參均傳值啸臀,但如果要拷貝整個數(shù)組届宠,開銷太大,而且絕大多數(shù)情況下乘粒,也不需要豌注。

3. 數(shù)組可以當作指針的情況總結(jié)

  1. a[i]這樣的形式對數(shù)組訪問總是被編譯器解釋成按*(a+i)的指針訪問
  2. 指針始終都是指針,它不會改寫成數(shù)組灯萍。但可以用下標形式訪問指針轧铁。
  3. 在作為函數(shù)的參數(shù)時,一個數(shù)組的聲明可以看作是指針旦棉。作為函數(shù)參數(shù)的數(shù)組始終會被編譯器修改成為指向數(shù)組第一個元素的指針齿风。
  4. 當把一個數(shù)組定義為函數(shù)參數(shù)時,可以把它定義為數(shù)組绑洛,也可以定義為指針救斑。不管選擇哪種方式,在函數(shù)內(nèi)部事實上獲得的都是一個指針真屯。
  5. 在其他所有情況下脸候,定義和聲明必須一致。

四、總結(jié)一下


理解了數(shù)組和指針何時可以互換何時必須各自使用之后运沦,對于下面語句的含義應該很清楚了:

/* 數(shù)組名和指針都可以作為函數(shù)的參數(shù)使用 */
char a[10];
char *p;

i = strlen(a);
j = strlen(p);

/* 這個語句清晰地展示了數(shù)組和指針的可互換性 */
printf("%s %s", a, p);

/* 下面這行語句要仔細理解其原理哦 */
printf("array at location 0x%x holds string %s", a, a);

/* 下面兩行可互換的程序也超常見 */
int main(int argv, char *argv[]);
int main(int argv, char **argv);

今后泵额,程序出現(xiàn)錯誤時認真分析,避免因誤用導致的程序錯誤携添,遇到指針和數(shù)組混合使用的情況時多多總結(jié)嫁盲,相信一定會有更多收獲和進步!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烈掠,一起剝皮案震驚了整個濱河市羞秤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌向叉,老刑警劉巖锥腻,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異母谎,居然都是意外死亡瘦黑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門奇唤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幸斥,“玉大人,你說我怎么就攤上這事咬扇〖自幔” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵懈贺,是天一觀的道長经窖。 經(jīng)常有香客問我,道長梭灿,這世上最難降的妖魔是什么画侣? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮堡妒,結(jié)果婚禮上配乱,老公的妹妹穿的比我還像新娘。我一直安慰自己皮迟,他們只是感情好搬泥,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伏尼,像睡著了一般忿檩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爆阶,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天燥透,我揣著相機與錄音代赁,去河邊找鬼。 笑死兽掰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的徒役。 我是一名探鬼主播孽尽,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼忧勿!你這毒婦竟也來了杉女?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鸳吸,失蹤者是張志新(化名)和其女友劉穎熏挎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晌砾,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡坎拐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了养匈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哼勇。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖呕乎,靈堂內(nèi)的尸體忽然破棺而出积担,到底是詐尸還是另有隱情,我是刑警寧澤猬仁,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布帝璧,位于F島的核電站,受9級特大地震影響湿刽,放射性物質(zhì)發(fā)生泄漏的烁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一叭爱、第九天 我趴在偏房一處隱蔽的房頂上張望撮躁。 院中可真熱鬧,春花似錦买雾、人聲如沸把曼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗤军。三九已至,卻和暖如春晃危,著一層夾襖步出監(jiān)牢的瞬間叙赚,已是汗流浹背老客。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留震叮,地道東北人胧砰。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像苇瓣,于是被迫代替她去往敵國和親尉间。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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