C語言 陣列和指標的區別

2021-07-02 04:43:22 字數 4363 閱讀 6804

實際上關於陣列與指標的區別這個問題在《c專家程式設計》已經有很詳細的闡釋,但我想用自己的語言說一說我的理解。

最近在做資料結構課設,其中乙個函式發生了令人費解的錯誤,簡化後的**如下:

#include 

int main()

程式執行到 printf 語句後便會掛掉,除錯時會提示乙個sigsegv訊號,根據原來的經驗,這時程式試圖訪問本不應該訪問的記憶體。

原來在 c 語言課堂上老師經常提到陣列就是乙個指標,指標也可以像陣列那樣用使用中括號的方式來進行記憶體訪問。以這樣的想法來分析前面的程式:foo 是乙個字元指標,即 foo 的值即為「abcde」的首字元「a」的位址,*foo 即為 『a』;那麼 foo 這個指標一定存在某個記憶體單元,&foo獲得這個記憶體單元的位址,即 pfoo 是指向 foo 的指標,那麼*pfoo 得到 foo,*(*pfoo)應該得到『a』了;這樣理解的話,程式是不應該有問題的。

下面我們使用指標代替陣列來實現上面的程式:

#include 

#include

int main()

程式這次執行結果和預料的相同,輸出乙個字母a。由此可見,陣列就是指標,這種說法是錯誤的。

有人認為陣列是乙個靜態常量,即陣列名代表乙個靜態的位址值,在編譯時確定,下面**可以證偽這種說法

int main()

; static

char *p = foo;

return

0;}

使用 gcc 編譯時會有以下錯誤:

notconstant

可見陣列名並不是代表乙個靜態量,並非位址常量。如果定義 foo 時加上 static 限定符,編譯就會通過,此時陣列名才代表了乙個靜態量。

請看以下**:

int main()

; 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 bar = 1;

return

0;}

使用 gcc 將其彙編並以 intel 格式輸出組合語言檔案:

gcc -s

-masm

=intel 1.c

關鍵部分為:

mov

dword

ptr[esp+8], 1

movdword

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

int main()

彙編的關鍵部分為:

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()

那個賦值語句一定會觸發乙個的錯誤,我們可以根據編譯輸出來確定它們的型別,錯誤為:

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))就會導致非法訪問的錯誤。

int main()

出錯資訊為:

error: incompatible types when assigning to

type

'int[1]' from type

'int *'

foo 作為右值時返回了乙個 int *,就是這個特性給人造成了陣列就是指標的假象。

C語言陣列指標和指標陣列的區別

對指標陣列和陣列指標的概念,相信很多c程式設計師都會混淆。下面通過兩個簡單的語句來分析一下二者之間的區別,示例 如下所示 int p1 5 int p2 5 首先,對於語句 intp1 5 因為 的優先順序要比 要高,所以 p1 先與 結合,構成乙個陣列的定義,陣列名為 p1,而 int 修飾的是陣...

C語言 陣列指標和指標陣列的用法 區別

2.指標陣列 陣列指標和指標陣列,從字面上區分的方法就是 本質看最後兩個字。例如陣列指標的本質就是乙個指標,乙個指向陣列的指標 而指標陣列的本質就是乙個陣列,乙個成員都是指標的陣列。陣列指標,即指向乙個陣列的指標。可以指向一維陣列,也可以指向二維陣列 三維及以上陣列一般不用 int p n null...

C 陣列指標和指標陣列的區別

陣列指標 也稱行指標 定義 int p n 優先順序高,首先說明p是乙個指標,指向乙個整型的一維陣列,這個一維陣列的長度是n,也可以說是p的步長。也就是說執行p 1時,p要跨過n個整型資料的長度。如要將二維陣列賦給一指標,應這樣賦值 int a 3 4 int p 4 該語句是定義乙個陣列指標,指向...