C語言:陣列和指標的區別
轉載請註明來源 http://blog.csdn.net/imred/article/details/45441457
實際上關於陣列與指標的區別這個問題在《C專家程式設計》已經有很詳細的闡釋,但我想用自己的語言說一說我的理解。
陣列是指標?
最近在做資料結構課設,其中一個函式發生了令人費解的錯誤,簡化後的程式碼如下:
#include <stdio.h>
int main()
{
char foo[] = "abcde";
char **bar = &foo;
printf("%c\n", *(*bar));
return 0;
}
程式執行到 printf 語句後便會掛掉,除錯時會提示一個SIGSEGV訊號,根據原來的經驗,這時程式試圖訪問本不應該訪問的記憶體。
原來在 C 語言課堂上老師經常提到陣列就是一個指標,指標也可以像陣列那樣用使用中括號的方式來進行記憶體訪問。以這樣的想法來分析前面的程式:foo 是一個字元指標,即 foo 的值即為“abcde”的首字元“a”的地址,*foo 即為 ‘a’;那麼 foo 這個指標一定存在某個記憶體單元,&foo獲得這個記憶體單元的地址,即 pfoo 是指向 foo 的指標,那麼*pfoo 得到 foo,*(*pfoo)應該得到‘a’了;這樣理解的話,程式是不應該有問題的。
下面我們使用指標代替陣列來實現上面的程式:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *foo = (char *)malloc(sizeof (char) * 2);
*foo = 'a';
*(foo + 1) = 0;
char **pfoo = &foo;
printf("%c\n", *(*pfoo));
return 0;
}
程式這次執行結果和預料的相同,輸出一個字母a。由此可見,陣列就是指標,這種說法是錯誤的。
陣列是靜態常量指標(static/Compile-time constant)?
有人認為陣列是一個靜態常量,即陣列名代表一個靜態的地址值,在編譯時確定,下面程式碼可以證偽這種說法
int main()
{
char foo[] = {'a'};
static char *p = foo;
return 0;
}
使用 gcc 編譯時會有以下錯誤:
error: initializer element is not constant
可見陣列名並不是代表一個靜態量,並非地址常量。如果定義 foo 時加上 static 限定符,編譯就會通過,此時陣列名才代表了一個靜態量。
陣列是動態常量指標(const/Runtime constant)?
請看以下程式碼:
int main()
{
char foo[] = {'a'};
char * const bar; //為什麼是這種寫法,請自行查閱相關資料
char *baz;
foo = baz; /* 1 */
bar = baz; /* 2 */
return 0;
}
gcc 編譯時錯誤資訊為:
/* 1 */ error: incompatible types when assigning to type 'char[1]' from type 'char *'
/* 2 */ error: assignment of read-only variable 'bar'
1 2 兩處出錯資訊並不相同,若陣列為動態常量指標,出錯資訊應像 2 那樣。
陣列是什麼?
陣列既不是靜態常量,也不是指標,那麼陣列是什麼?
左值和右值
首先補充一些左值和右值的知識,引用《C專家程式設計》中的一段話:
出現在賦值符左邊的符號有時被稱為左值,出現在賦值符右邊的符號有時被稱為右值。編譯器為每個變數分配一個地址(左值)。這個地址在編譯時可知,而且該變數在執行時一直儲存於這個地址。相反,儲存於變數中的值(它的右值)只有在執行時才可知。如果需要用到變數中儲存的值,編譯器就發出指令從指定地址讀入變數值並將它存於暫存器中。
我對左值的理解和書上有些區別,我把這裡的“符號”稱為“物件”,每一個符號都代表一個物件,物件與地址是一一對應的。即如果宣告瞭 int a,那麼 a 作為一個左值時,a 即代表這個儲存在某個特定的地址的物件,對這個物件賦值即為把值放在這個特定的地址;a 作為右值時即代表 a 的內容,就是一個單純的值,而不是物件。一個值是不能作為左值的,比如一個常數 1, 1 = a 這樣的賦值語句是無法編譯通過的。在我看來,“左值”義同“物件”,“右值”義同“值”,所以下面“左值”和“物件”指的是相同的東西。但是“左值”又有一個子集:“可修改的左值”,只有這個子集中的東西才能放在賦值號左邊,因此我認為將引用中的第一句話修改為“出現在賦值符左邊的符號有時被稱為可修改的左值”更能表達其實際的意思。為什麼要引出這個子集,為的就是要把陣列分出來,陣列是左值,但並不是可修改的左值,因此你也不能直接把陣列名放在等號左邊進行賦值。
陣列就是陣列!
我先把結論放在這裡,然後在進行分析:陣列就是陣列,一個陣列名就代表一個陣列物件,這個物件內可以有一個或多個元素,每個元素型別都相同;正如 int 就是 int,一個 int 變數名就代表一個 int 型別物件。看到這裡,你可能要笑了,這不是什麼都沒說嗎,誰不知道陣列是這個意思啊,我想知道陣列和指標什麼關係。其實對陣列的認識就是這樣一個返璞歸真過程,看我來慢慢解釋。
以下程式碼:
/* 1.c */
int main()
{
int foo[] = {1};
int bar = 1;
return 0;
}
使用 gcc 將其彙編並以 intel 格式輸出組合語言檔案:
gcc -S -masm=intel 1.c
關鍵部分為:
mov DWORD PTR [esp+8], 1
mov DWORD PTR [esp+12], 1
esp+8 位置就是那個 int foo[],esp+12 位置就是那個 int bar。可見,給 int 陣列的賦值時就像給一個 int 變數賦值一樣,並沒用指標來進行間接訪問,這個 int 陣列物件 foo 的記憶體地址在編譯時就確定了,是 esp+8;正如那個 int 物件 bar 一樣,它的記憶體地址在編譯時也確定了,是esp+12。
以示區別,我將下面程式碼同樣以組合語言輸出:
/* 2.c */
#include <stdlib.h>
int main()
{
int *foo = (int *)malloc(sizeof (int));
*foo = 1;
return 0;
}
彙編的關鍵部分為:
mov DWORD PTR [esp], 4
call _malloc
mov DWORD PTR [esp+28], eax
mov eax, DWORD PTR [esp+28]
mov DWORD PTR [eax], 1
前兩句為 foo 分配記憶體空間,第三句將分配的記憶體空間地址值賦給 foo,foo 的地址為 esp+28,編譯時已知。下面是賦值部分,首先從 foo 那裡得到地址值,然後向這個地址賦值,這裡可以看出和給陣列賦值的差別,給陣列賦值時是將值直接賦到了陣列中,而不用從哪裡得到陣列的地址。
由上面可以看出,陣列更像一個普通的變數,編譯時就知道了其地址,可以直接賦值。
陣列作為左值
陣列不能放在賦值號左邊,但陣列仍可以作為一個左值或者說物件出現在語句中,一個重要的例子就是取地址操作:&。取地址操作 &的運算元必須是一個左值,而不能是一個右值。比如一個變數int a = 1,&a 就可以得到 a 的地址,但 &1 是非法的,一個單純的數值是沒有地址的。那麼對於一個int foo[],&foo 會返回一個什麼樣的值呢?自然是一個指向陣列的指標咯,下面的程式可以看出來:
int main()
{
int foo[1];
int bar[1];
bar = &foo; //故意觸發一個 error
return 0;
}
那個賦值語句一定會觸發一個的錯誤,我們可以根據編譯輸出來確定它們的型別,錯誤為:
error: incompatible types when assigning to type 'int[1]' from type 'int (*)[1]'
沒錯,&foo 返回資料型別為 int (*)[1],就是一個指向陣列的指標。指向陣列?指向陣列的哪裡呢?指向陣列物件首地址,正如一個指向 int 物件的指標指向那個 int 物件佔有的兩個或四個記憶體單元的首地址一樣。
把 &foo 賦給一個普通的指標是可以的,不過會觸發一個 warning,因為int * 與 int (*)[1] 並不相容。賦值後普通指標的值與 &foo 的值是相同的,都是陣列物件的首地址,只是普通指標把這塊記憶體當做 int 物件處理而已。
由於 C 語言是弱型別語言,你把 &foo 賦給int **********bar 或者 int *baz都是可以的,都不會導致 error,只會導致 warning,此時你列印出 *bar 或者 *baz 的值都是 foo 中第一個整數的值(前提是指標和陣列佔用空間大小相等)。正如文章開頭的程式碼那樣,以這個整數的值作為一個地址值進行間接訪問(*(*bar))就會導致非法訪問的錯誤。
陣列作為右值
陣列作為右值時會發生什麼?返回陣列物件內的所有值自然不可能,因此 C 語言中採取的方法是陣列作為右值時返回物件中元素型別的指標,指標指向第一個元素,類似上一個例子:
int main()
{
int foo[1];
int bar[1];
bar = foo; //故意觸發一個 error
return 0;
}
出錯資訊為:
error: incompatible types when assigning to type 'int[1]' from type 'int *'
foo 作為右值時返回了一個 int *,就是這個特性給人造成了陣列就是指標的假象。
總結
陣列作為左值和陣列作為右值時的區別造成了無數人的困惑與誤解:foo 作為右值時確實等價於一個指標,因為陣列無法像普通物件那樣返回它的值,它的元素可能有成百上千個,但作為一個左值時——比如作為取地址操作符的運算元時,陣列就是作為一個陣列物件而出現的,而不是指標,取地址返回一個指向陣列的指標,而不是指向指標的指標。
一句話總結就是:陣列就是陣列,有著自己的特性。
(題外話:從生成的組合語言看,用指標來訪問記憶體實際上並不比使用陣列來訪問記憶體快,反而是慢了)
轉載請註明來源 http://blog.csdn.net/imred/article/details/45441457
相關文章
- C語言指標(三):陣列指標和字串指標C語言指標陣列字串
- C語言 指標與陣列C語言指標陣列
- C語言指標和陣列筆試題C語言指標陣列筆試
- Golang 學習——陣列指標和指標陣列的區別Golang陣列指標
- c語言-運算子,陣列,指標C語言陣列指標
- (C語言)使用指標列印陣列的內容C語言指標陣列
- C陣列和指標陣列指標
- 指標陣列和陣列指標與二維陣列指標陣列
- c語言的陣列C語言陣列
- C語言指標C語言指標
- C語言陣列C語言陣列
- 陣列指標,指標陣列陣列指標
- C語言系列之 指標與陣列總複習視訊教程C語言指標陣列
- C語言使用指標對陣列指定位置進行插入元素C語言指標陣列
- C指標和陣列的關係詳解指標陣列
- 【C++系列】指標物件和物件指標的區別C++指標物件
- Go 陣列指標(指向陣列的指標)Go陣列指標
- 【C】 28_指標和陣列分析(上)指標陣列
- 【C進階】28、指標和陣列分析指標陣列
- 指標陣列與陣列指標指標陣列
- c語言字元陣列C語言字元陣列
- C/C++引用和指標的聯絡和區別C++指標
- C語言知識彙總 | 51-C語言字串指標(指向字串的指標)C語言字串指標
- C++語言程式設計筆記 - 第6章 - 陣列、指標與字串C++程式設計筆記陣列指標字串
- 陣列地址與指標之間的區別與聯絡陣列指標
- c語言指標彙總C語言指標
- C語言指標用法大全C語言指標
- C語言 函式指標C語言函式指標
- C語言指標筆記C語言指標筆記
- C語言基礎-指標C語言指標
- C語言指標學習C語言指標
- C語言 第 7 節 如何將字串指標指向的內容賦值給陣列C語言字串指標賦值陣列
- C語言中陣列首地址和陣列第一個元素的地址有什麼區別C語言陣列
- 指標常量和常量指標的區別指標
- C語言指標(二) 指標變數 ----by xhxhC語言指標變數
- sizeof和strlen計算陣列型別和指標型別字串陣列型別指標字串
- c語言中陣列的宣告喝初始化的區別和聯絡C語言陣列
- C語言知識彙總 | 56-C語言NULL空指標以及void指標C語言Null指標
- 陣列指標陣列指標