1. 程式人生 > >《C和指標》讀書筆記(5)

《C和指標》讀書筆記(5)

宣告:該讀書筆記摘抄自《C和指標》——Kenneth A.Reek (著)    徐波(譯)。為了克服自己走馬觀花,提高閱讀和學習效率,決定將自己在讀書過程中遇到的一些知識點加以摘抄和總結備忘,在此感謝原書作者和翻譯。

一、有助於理解C語言指標的幾個基礎概念

1、左值:賦值號“=”左邊的運算元;      右值:賦值號“=”右邊的運算元。

      2、變數的值:分配給該變數的記憶體位置儲存的值,對指標變數來說它的值就是某個記憶體地址;

      3、變數的地址:編譯器為該變數分配的一個記憶體位置,用於儲存變數本身,注意變數的地址和變數的值的區別,我想說的是對於普通變數很好理解,難點在於多級指標。

      4、間接訪問(也叫解引用):一個指標訪問它所指向的地址,間接訪問操作符 “ * ” 。注意:間接訪問僅僅是指定了一個特定的記憶體地址而已。      

      5、當變數作為左值使用時,它表示一個記憶體地址; 當變數作為右值使用時它表示該變數裡面儲存的內容。真正地理解了這一點有助於理解指標的複雜應用和原理。

舉個例子:

        int   a = 1, b = 2,  c = 8,*p = &b;

        *p = *p  +1;

          printf("b = %d\n", b);

          p = p + 1;

          printf("*p = %d\n", *p);

      第一個printf結果很好分析,*p作為右值的時候,最終的結果是取出了p所指向的地址的內容,但是這裡麵包含了兩個過程,第一步:*p表示間接訪問,這一步確定了變數b,至於是訪問b的地址還是它儲存的內容,暫時還不清楚;第二步:*p在表示式中是個右值,因此編譯器會確定訪問它的內容。注意:左值和右值得區分是由編譯器自動完成的。因此,第一個列印結果是 3 。

       現在來看第二個,這整個例子是我自己寫的,在linux-gcc下測試。 其實在我寫的這個例子中p = p + 1;這個語句其實是非法的,因為指標的算術運算僅僅在記憶體地址連續分配的陣列中才有意義,這裡我僅僅是為了舉例子演示而已。 

現在來分析一下p = p + 1 這個表示式:當p作為右值時,編譯器會訪問指標變數p儲存的內容,也就是b的地址,然後加1,這裡又涉及到了指標的算術運算,要考慮步長的問題,p = p + 1 其實是 p = p + 1*(p所指向的物件的型別佔用的位元組數);p指向的物件是b,b宣告為int型別,因此這裡的加1實際上在記憶體中在變數b的基礎上連續移動了4個位元組。 那麼它是向“前”移動還是向“後”移動呢?這裡又會涉及到另一個知識點,就是關於棧的生長方向的問題。這裡還要說明一點,就是在程式的編譯過程中,編譯器會根據程式中宣告變數的順序對其地址連續分配。例如在上例中,編譯器會先給a變數分配一個地址,然後緊接著a的地址再給變數b分配,再給變數c分配,最後給指標變數p分配,至於他們的地址是不斷增加還是減少,這就要看棧的生長方向了。在X86  linux下gcc編譯測試,X86棧生長方向是向下的,即編譯器在確定變數的地址時,從高地址向低地址分配使用記憶體棧空間,這樣在上例中,a的地址最大,假設為0x2000001C,那麼變數b的地址減小4,為0x20000018,變數c的地址為0x20000014,變數p的地址為0x20000010。  有了這些概念之後,我們就可以得出答案了,在p = p + 1表示式中,p + 1 就是變數p的值加4,變數p的值就是變數b的地址,因此p + 1的結果就是0x20000018 + 4 == 0x2000001c,可以看出,這是變數a的地址。然後在表示式p = p + 1中,當p作為左值使用時,編譯器會將其識別為變數p自己的地址,執行完p = p + 1 之後, 變數p 的值已經變成變數a的地址,即指標變數p已經指向了變數a,在printf中再對指標變數p進行間接訪問,這時將會訪問指標變數p所指向的地址的內容,即變數a的值,結果為1。

      注:在上例中,我之所以在變數b前面定義變數a是為了保證讓p + 1合法,也是為了驗證棧的生長方向;在b的後面定義變數c僅僅是為了驗證棧的生長方向。

二、指標表示式

     這部分內容在筆試題目裡面出現的次數很多,很容易混淆,究其原因主要是沒有弄清楚指標運算的本質過程,一條簡單的C語句有可能是好幾條彙編指令的集合過程,如果有條件可以從彙編的角度去理解。

     這裡定義幾個實驗變數:char      a = '1',b = '2',c = '3',  *p = &b;後面就用它們來舉例說明幾個指標表示式的執行過程:

     1、 p++     

       第一步:該表示式首先返回指標變數p的一份拷貝,該拷貝將會被存放到記憶體的某一個地方以便後面使用,注意,這裡說“某一個”是因為我們並不能確定具體的儲存位置,這是一個表示式內部計算的中間過程,存放位置也是一個臨時位置,這一點解釋了為什麼該指標表示式不能用作左值——因為拷貝值的儲存位置不確定。因此p++只能充當右值。第二步:表示式返回一份拷貝後,將指標變數p自身的值加1。可以看出,該表示式實際上是執行了兩個過程,在第一步執行完了以後,如果該指標的拷貝會被用到,例如 char    *q = p++;那麼該拷貝將會賦值給指標變數q。如果該拷貝不會被用到,例如  只有 一句p++;語句,那麼該拷貝值將會被簡單丟棄。

    2、++p

        第一步:該表示式首先將指標變數p的值加1,;第二步:產生一份加1之後的拷貝,產生的這份拷貝也將會被儲存與記憶體的某一個位置留待後用或者不用被簡單丟棄,這一點也解釋了改表示式同樣不能作為左值使用。

   3、*++p

       ++優先順序高於間接訪問符,先執行++p,++p的執行過程已經解釋過了,它的結果是增值之後的指標的拷貝,然後間接訪問操作符再作用於該指標拷貝上,這樣得到的結果將是對增值後的指標的間接訪問,它可以作為左值也可以作為右值,具體說明見第一部分第5條。

   4、*p++

       這個表示式會引起誤解:++優先順序高於間接訪問符,因此先執行p++,然後間接訪問符作用於增值後的指標變數。這是錯誤的。   ++優先順序高於間接訪問符這句話沒錯,先執行p++也沒錯,錯就錯在了間接訪問符的作用物件上,它其實是間接訪問增值前的指標。表示式執行過程分解:第一步:++操作符產生指標變數p的一份拷貝先暫時存放到某個位置;第二步:++操作符增加指標變數p的值;第三步:在指標變數p的拷貝上執行間接訪問操作,注意是在拷貝上執行間接訪問操作,即間接訪問的是增值前的指標。它可以作為左值也可以作為右值,具體說明見第一部分第5條。

    5、++*p

       ++和間接訪問符的結合性從右到左,首先執行對p的間接訪問,然後將間接訪問的內容增1,最後表示式返回一份內容增1後的拷貝。只能做右值,不能做左值。理由同p++。

   6、++*++p

      三個操作符結合性都是從右到左。*++p已經分析過,它返回對增值後的指標的間接訪問。然後對*++p間接訪問的內容進行增1操作,最後返回內容增1後的值的拷貝。只能做右值,不能做左值。理由同p++。

  7、++*p++

      *p++已經分析過了,它的結果是間接訪問增值前的指標。然後對間接訪問的內容進行增1操作,返回內容增1後的值的拷貝。只能做右值,不能做左值。理由同p++。

三、指標的算術運算和關係運算

   1、指標的算術運算有兩種形式,標準定義這兩種定義只能用在連續儲存的同一陣列中。第一:指標加上(或者減去)一個整數,此時需要考慮步長;第二:指標減去指標,這裡特別注意,兩個指標相減時得到的結果是相距的距離,即兩指標相減後還要除以步長,它是指標加減法的逆運算。

   2、關係運算

    >  >=    <    <=   !=  

   前面4個只能用在同一個陣列中比較, !=可以用於任意兩指標比較。 

花了三個多小時來理清指標的思路,儘量表達清楚和全面,都已經深夜了,睡了。

以上是第六章的摘抄和總結,未完待續。。。。。