有一個這樣的錯誤:
在一個文件中定義:int mango[100];
在另一個文件中聲明:extern int *mango;
將會產生錯誤
定義和聲明的區別:
在C中,任何對象都有且只有一個定義,但是可以有多個聲明
- definition:只出現一次 爲一個對象指定類型,分配存儲空間。用於創建一個新的對象
- declaration:可以出現多次 描述這個對象的類型。用於引用某個已經定義了的對象
所以數組的定義需要指定大小,聲明不需要。但是對於多維數組,需要在聲明的時候指定除最左側維度的其他維度的大小,這樣編譯器才知道怎麼解析下標
地址y和地址y的內容:
雖然在大多數語言中二者使用相同的符號表示,但是二者有不同的含義
以一個賦值語句爲例:x=y
- 符號x:在這條語句中表示的是x這個符號代表的內存地址,這個地址被稱爲左值。左值在編譯期間確定,指明存儲值的位置
- 符號y:在這個語句中表示的是y代表的地址的內容,這個內容被稱爲右值。右值直到運行期間纔可以確定,"y的值"表示右值
"可修改左值"是C引進的術語
- 表示一個左值可以放到一個賦值語句左側(並不意味之左值本身代表的地址值可以被改變)
- 這個概念爲了兼顧數組名,數組名是一個指明對象位置的左值,但在C中不能被賦值,所以數組名是左值但不是可修改左值
編譯器會爲x和y都分配一個地址空間,也就是左值。這兩個左值在編譯期間確定,並用於在運行期間存放變量
但是這兩個左值中存放的值只有在運行期間纔可以確定,當需要用到存儲在變量中的值的時候,編譯器執行命令獲取存儲在地址空間中的值放到寄存器中
關鍵的區別在於:
因爲編譯期間就可以確定每個符號,所以如果編譯器想要對某個地址(左值)進行操作就可以直接去做而不用執行指令去獲取這個地址
而一個指針的值(右值)只有在運行期間纔可以知道並被解引用
指針中存放的地址可以隨便更改,地址指向的存儲空間中的值也可以隨便更改,但是數組的存儲位置在程序運行期間不會改變,即便存儲的值會變
所以聲明一個數組和聲明一個指針的區別在於指針需要在運行期間使用額外的指令去獲取指針中存放的值,而數組是編譯器已經知道的
錯誤的原因:
定義是數組,所以mango代表的地址空間以及之後的一片空間都直接存放的是char字符
引用是指針,編譯器執行指針的解析方式:獲取mango代表的地址空間,獲取它裏面存放的值,然後把這個值當作地址並加上偏移之後獲取對應位置的值
但是實際上mango代表的地址空間中的值是一個ASCII字符,編譯器解析爲指針就會產生錯誤
正確的方式:
file 1:
int mango[100];
file 2:
extern int mango[];
字面量初始化:
數組和指針都可以用一個字面量初始化,但是二者的效果完全不同
指針定義時只會申請一個空間存放地址值而不會爲初始化的值申請空間。ANSI標準規定用字面量初始化的指針是隻讀的,通過指針進行的更改是未定義的。一些編譯器會把字面量放到程序的文本段中,那裏的數據是受只讀權限保護的、
但是數組會爲字面量申請空間並存放,所以對它的修改是合法的
實際使用時的數組和指針:
當正常聲明一個數組的時候,會發現編譯器爲這個數組分配的空間正好是數組元素佔用的空間,不會爲數組名分配有額外的空間
如果聲明一個N維數組,你會發現所有維的第一個元素都指向分配的第一個內存地址
如果嘗試對數組名進行取地址操作,那麼你仍將得到分配的第一個內存地址。但是這個地址中存放的值其實是數組的第一個元素,而不是數組名代表的一個地址類型的值。所以並不會有一個內存地址(開發者可見的,當然編譯器會爲自己記錄一下符號表之類的值)中存放着數組名,也就是數組名只是被編譯器維護在符號表中的,而不存在一個實際的變量。編譯器在維護的時候將它的地址和它的值都設置爲分配的第一個內存地址。
所以,顯然的,數組名不能被當作左值,任何將數組名當作左值的操作都會使編譯器發出非左值的error
但是如果是一個指針,無論是通過將現有的數組賦給它還是動態申請內存來爲它賦值,它本身都有一個實實在在的內存空間用於維護自身,所以它可以作爲左值
函數傳數組:
編譯器在編譯的時候會爲參數和返回值申請棧空間
所以實際上是有實在的空間存儲參數的
在向函數傳遞數組參數的時候,會向變量中實際傳遞這個數組名代表的值,因而這個參數佔有實際的存儲空間,這就成爲一個指針了,所以這裏的“數組名”將可以作爲左值
這也是合理的,函數維護這個變量到底是一個數組還是一個指針將會花費額外的精力,在爲參數申請棧空間的時候也要分情況考慮,如果使用一個指針,這將簡單的多
但是編譯器只會進行一重這樣的退化,當參數是多維數組時,函數實際得到的是指向維數少一的數組的指針
這個指針的類型就是數組元素類型的指針,但是在獲取它的大小的時候會有一個warring:
warning: 'sizeof' on array function parameter 'a' will return size of 'const double *' [-Wsizeof-array-argument]
到底還是一個指針
動態分配的數組就不會具有這樣的性質,因爲它們畢竟還是指針,每一重都會多佔用一部分內存地址用於存放指針,但是數組就不會