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

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

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

 一、char型別有無符號取決於編譯器的實現

(受本科時一本教材的影響,我以前一直以為char就是有符號的,只是省略了signed而已)。我在linux gcc下測試發現gcc編譯器將char型別實現為了有符號的,範圍為-128~127。arm-linux-gcc將char實現為了無符號的,範圍0~255。mipsel-linux-gcc將char實現為有符號的,範圍-128~127。測試方法:定義一個char型別變數,並且用一個最高位為1的字面值常量將其初始化,然後比較該變數和該字面值常量,若相等,則該編譯器將其char型別實現為unsigned,否則實現為signed。

        char    c = 0x80;
  
        if (0x80 == c) {
             printf("gcc char is unsigned\n");
        } else {
              printf("gcc char is signed\n");

         }

這個問題主要是出於移植性方面的考慮,原書作者建議將char變數值限制在signed和unsigned的交集內使用,也就是0~127(即用低7位二進位制數表示的基礎ASCII碼),並且只有在char型別顯示宣告為signed或者unsigned時才對其進行算術運算,這是個不錯的建議。

二、變數的三個屬性——作用域、連結屬性、儲存型別,這三個屬性決定了一個變數的“可視性”(即它在什麼地方可以引用)和生命週期(即它的值將儲存多久)。(注:直接引用於原文)

       在讀到這一部分內容之前,自己已經把extern、static等關鍵字用的相當熟練,並且想起自己當初找工作時簡歷上赫然寫過“精通C語言”等字樣,做筆試時看到類似題目甚至還嗤之以鼻,但是讀完這一部分內容,並且是花了一個多小時我認認真真讀了兩遍之後,不禁滿臉羞愧,原來自己的某些理解是錯的,並且錯的離譜。究其原因,沒搞懂作用域和連結屬性,甚至之前都不知道變數有個連結屬性。

1、作用域。編譯器可以確認4種不同型別的作用域,分別是檔案作用域、函式作用域、程式碼塊作用域、原型作用域。

識別符號宣告的位置決定它的作用域。注:(前面這兩句話直接摘抄於原文)。特別注意第二句話,作用域僅僅是由識別符號的宣告位置決定的。1)程式碼塊作用域。一個程式碼塊就是用一對花括號包起來的那部分語句,程式碼塊作用域從程式碼塊的開始位置開始到配對的花括號處結束。程式碼塊允許巢狀,當內外層分別出現同名識別符號時,內層識別符號隱藏外層識別符號,即只有內層識別符號起作用(當然,這種情況應該避免)。2)檔案作用域。宣告在所有程式碼塊之外的識別符號都具有檔案作用域,包括函式名。它們的作用域從在本檔案的宣告處開始到本檔案結束,但有一個例外,就是用#include標頭檔案包含的全域性識別符號,它們的作用域延伸到包含它們的檔案(從被包含的地方起一直到檔案結束)。這一點也解釋了為什麼在函式呼叫時,被呼叫的函式必須事先宣告或定義。3)原型作用域,只適用於函式原型宣告的形參名。函式宣告中的形參名非必需,也不必與函式定義的形參名形同,更不必與被呼叫時傳遞的實參名相同。原型作用域防止函式宣告時的形參名與程式其他地方的變數名衝突,其實,只要在同一個函式宣告中不使用兩個或以上相同的形參名就可以了。4)函式作用域,只適用於goto的語句標籤,在一個函式內,goto的語句標籤必須唯一。

2、連結屬性,共三種:extern、internal、none(即沒有連結屬性)。識別符號的連結屬性是為了處理不同檔案中出現的同名識別符號。識別符號的作用域與連結屬性有關,但這兩個屬性並不相同。(注:以上三句話直接摘抄於原文)。我的誤區就在這裡。作者有三句話比較經典:1)沒有連結屬性的識別符號總是被當做獨立不同的實體; 2)internal連結屬性的識別符號在同一檔案內代表同一個實體,不同檔案內的internal連結屬性識別符號代表不同的實體 ;3)extern連結屬性識別符號不論宣告多少次,在不同檔案內都表示同一個實體。再說說哪些識別符號的連結屬性是extern、哪些的是internal、哪些識別符號沒有連結屬性,作者原文中是以示例說明,我這裡概括了一下:1)凡是具有檔案作用域的識別符號(注意包括函式名),其預設連結屬性是extern,其餘的識別符號都沒有連結屬性;2)當對預設連結屬性為extern的識別符號使用static修飾符限定時,識別符號的連結屬性被改變為internal。特別注意,static修改連結屬性時僅僅對預設連結屬性為extern的識別符號有效,這一點可以區別以下情況:當給一個具有程式碼塊作用域的識別符號(即一個區域性變數)加以static修飾時,它不會改變該識別符號的連結屬性,因為根據上面兩條中第一條,一個只具有程式碼塊作用域的識別符號是沒有連結屬性的,不是extern,因此第二條規則無效。這種情況下,static僅僅改變了程式碼塊識別符號的儲存型別而已。最後,當extern關鍵字用於原始檔中一個識別符號的第一次宣告時,該識別符號具有extern連結屬性,但是如果extern用於一個識別符號的第二次或以後的宣告時,它不會修改由第一次宣告所指定的連結屬性,作者還舉了個例子,

         static  int   i; //在該原始檔中第一次聲明瞭一個變數i,它的連結屬性為internal

             int    func()

         {

                   int           j; 

                   extern    int       k;//在此之前,k沒有被宣告過,第一次被extern宣告,它的連結屬性就是extern

                   extern    int        i;//在此之前,i已經被static宣告為了internal連結屬性,此時extern不會改變其連結屬性

         }

3、儲存型別,指定儲存變數值的記憶體型別(動態記憶體(即堆疊)還是靜態記憶體)或位置,它決定變數何時建立、何時銷燬,即決定變數的生命週期。共有三個地方可以儲存變數:普通記憶體、執行時堆疊、硬體暫存器。1)具有檔案作用域的變數儲存在靜態記憶體中,它們在程式執行之前就被建立,直到程式執行結束;2)具有程式碼塊作用域的變數預設儲存型別為自動的(auto),儲存於執行時堆疊,但是若用static修飾會改變其儲存型別(注意,作用域仍然不變),該變數將會儲存於靜態記憶體中,在整個程式執行過程中它一直存在,並且只會初始化一次。在函式return時可以返回此類變數。還要注意,函式形參不可以宣告為static,因為實參總是通過執行時堆疊傳遞,用於支援遞迴。3)可以用register關鍵字宣告具有程式碼塊作用域的自動變數,用於將該變數儲存於硬體暫存器而不是記憶體中來提高變數的訪問效率。另外要注意,register的使用是否生效和編譯器有關,因為編譯器有可能會忽略該關鍵字,出現於以下兩種情況:1)如果宣告太多的暫存器變數,編譯器可能只取前幾個放進暫存器,後面的仍然當做自動變數處理;2)如果編譯器自己有一套暫存器優化方案,它有可能忽略register關鍵字。最後,暫存器變數的建立和銷燬時間個自動變數相同,但在函式執行之前和返回時它們需要堆疊來進行暫存器內容的保護(入棧)和恢復(出棧)。

以上為第三章內容,未完待續。。。。。。