1. 程式人生 > >C專家程式設計 十 規則2: C語言把陣列下標作為指標的偏移量(二)

C專家程式設計 十 規則2: C語言把陣列下標作為指標的偏移量(二)

        把陣列下標作為指標加偏移量足C語言從BCPL (C語言的祖先)繼承過來的技巧。在人們的常規思維中,在執行時增加對C語言下標的範圍檢查是不切實際的。因為取下標操作只是表示將要訪問該陣列,但並不保證一定要訪問。而且,程式設計師完全可以使用指標來訪問陣列,從而繞過下標操作符。在這種情況下,陣列下標範圍檢測並不能檢測所有對陣列的訪問的情況。事實上,下標範圍檢測被認為並不值得加入到C語言中。
還有一種說法是,在編寫陣列演算法時,使用指標比使用陣列“更有效率”。

        這個頗為人們所接受的說法在通常情況下是錯誤的。使用現代的產品質量優化的編譯器,一維陣列和指標引用所產生的程式碼並不具有顯著的差別。不管怎樣,陣列下標是定義在指標的基礎上的,所以優化器常常可以把它轉換為更有效率的指標表達形式,並生成相同的機器指令。讓我們再看一下陣列/指標這兩種方案,並把初始化從迴圈內部的訪問中分離出來。

int a [ 10] ,*p, i;

變數a[i]可以用圖9-2所示的各種方法來訪問,效果完全一樣。

        即使編譯器使用的是較原始的翻譯方法,兩者產生不一樣的程式碼,用指標迭代一個一維陣列常常也並不比直接使用下標迭代一個一維陣列來得更快。不論是指標還是陣列,在連續的記憶體地址上移動時,編譯器都必須計算每次前進的步長。計算的方法是偏移量乘以每個陣列元素佔用的位元組數,計算結果就是偏移陣列起始地址的實際位元組數。步長因子常常是2的乘方(如int是4個位元組,double是8個位元組等),這樣編譯器在計算時就可以使用快速的左移位運算,而不是相對緩慢的加法運算。一個二進位制數左移3位相當於它乘以8。 如果陣列中的元素的大小不是2的乘方(如陣列的元素型別是一個結構),那就不能使用這個技巧了。
        然而,迭代一個int陣列是人們最容易想到的。如果一個經過良好優化的編譯器進行程式碼分析,並把基本變數放在高速的暫存器中來確認迴圈是否繼續,那麼最終在迴圈中訪問指標和陣列所產生的程式碼很可能是相同的。
        在處理一維陣列時,指標並不見得比陣列更快。C語言把陣列下標改寫成指標偏移量的根本原因是指標和偏移量是底層硬體所使用的基本模型。


       上面這些例子顯示了不同的備選方案經過翻譯後所產生的中間程式碼。如果釆用優化措施,中間程式碼可能跟這裡顯示的不一樣。RO、R1等代表CPU的暫存器。在圖中,我們用

R0儲存p的左值 R1儲存a的左值或p的右值

R2儲存i的左值 R3儲存i的右值

       [R0]表示間接載入或寫入,其地址就是暫存器的內容(這是許多組合語言所使用的一個普通概念)。 “可以提到迴圈外”表示這個資料不會被迴圈修改,在每次迴圈時可不必執行該語句,可以加快迴圈的速度。

“作為函式引數的陣列名”等同於指標

規則3也需要進行解釋。

首先,讓我們回顧一下the C programming language中所提到的一些術語。


        標準規定作為“型別的陣列”的形參的宣告應該調整為“型別的指標”。在函式形參定義這個特殊情況下,編譯器必須把陣列形式改寫成指向陣列第一個元素的指標形式。編譯器只向函式傳遞陣列的地址,而不是整個陣列的拷貝。不過,現在讓我們重點觀察一下陣列,隱性轉換意味著三種形式是完全等同的。因此,在my_function()的呼叫上,無論實參是陣列還是真的指標都是合法的。
my_function(int *turnip) { .
my—function(int turnip[]) {
my一function(int turnip[200]) {