《C專家程式設計》:令人震驚的事實:陣列和指標並不相同
陣列和指標是如何訪問的
首先需要注意的是“地址y”和“地址y的內容”之間的區別。
X = Y;
在這個上下文環境裡
符號X的含義是X所代表的地址,這裡被稱為左值,左值在編譯時可知,左值表示儲存結果的地方
符號Y的含義是Y所代表的地址的內容,這裡被稱為右值,右值直到執行時才知,如無特別說明,右值表示“Y的內容”
C語言引入了“可修改的左值”這個術語。它表示左值允許出現在賦值語句的左邊。這個奇怪的術語是為與陣列名區分,陣列名也用於確定物件在記憶體中的位置,也是左值,但它不能作為賦值的物件。因此,陣列名是個左值但不是可修改的左值。標準規定賦值符必須用可修改的左值作為左邊一側的運算元。用通俗的話說,只能給可以修改的東西賦值。
編譯器為每個變數分配一個地址(左值)。這個地址在編譯時可知,而且該變數在執行時一直保存於這個地址。相反,儲存於變數中的值(它的右值)只有在執行時才可知。如果需要用到變數中儲存的值,編譯器就發出指令從指定地址讀入變數值並將它存入暫存器中。
這裡的關鍵之處在於每個符號的地址在編譯時可知。所以,如果編譯器需要一個地址(可能還需要加上偏移量)來執行某種操作,它就可以直接進行操作,並不需要增加指令首先取得具體的地址。相反,對於指標,必須首先在執行時取得它的當前值,然後才能對它進行解除引用操作(作為以後進行查詢的步驟之一)。
陣列的下標引用:
char a[9] = "abcdefgh"; c = a[i];
編譯器符號表具有一個地址9980
執行時步驟1:取i的值,將它與9980相加
執行時步驟2:取地址(9980 + i)的內容
9980 +1 +2 +3 +4..........+i
對指標的引用:
char *p;
c = *p;
編譯器符號表有一個符號p,它的地址為4624
執行時步驟1:取地址4624的內容,就是‘5081’
執行時步驟2:取地址5081的內容
當你“定義為指標,但以陣列方式引用”時會發生什麼
char *p = "abcdefgh";
c = p[i];
編譯器符號表具有一個p,地址為4624
執行時步驟1:取地址4624的內容,即 5081
執行時步驟2:取得i的值,並將它與5081相加
執行時步驟3:取地址[ 5081+i ]的內容
最後一種方式其實質是前兩種訪問方式的組合。先間接引用,在直接引用
- 取得符號表中p的地址,提取儲存於此處的指標
- 把下標所表示的偏移量與指標的值相加,產生一個地址
- 訪問上面這個地址
只要把p宣告為指標,那麼不管p原先是定義為指標還是陣列,都會按照上面所示的三個步驟進行操作,但只有當p原來定義為指標時這個方法才是正確的。
檔案1:
char p[10];
檔案2:
extern char*p;
這種情況,當用p[i]這種形式提取這個宣告的內容時,實際上得到的是一個字元,但按照上面的方法,編譯器卻把它當做指標,把ACSII解釋為地址顯示不對。
char *p = "abcdefgh"
p[3];
char *a = "abcdefgh"
a[3];
顯然這兩種情況下,都可以取得‘d’,但兩者的途徑不一樣
陣列與指標的其他區別
表三:陣列與指標的區別
陣列和指標都可以在它們的定義中用字串常量進行初始化,儘管看上去一樣,底層的機制卻不相同
定義指標時,編譯器並不為指標所指向的物件分配空間,它只是分配指標本身的空間,除非在定義時同時賦給指標一個字串常量進行初始化,在ACSI C中,初始化指標所建立的字串常量被定義為只讀,如果試圖通過指標修改這個字串的值,程式就會出現未定義的行為,是因為:指標p 指向常量字串(位於常量儲存區),常量字串的內容是不可以被修改的。
與指標相反,由字串常量初始化的陣列是可以修改的