C 語言筆記:保留關鍵字

什麼是保留關鍵字?

C 語言的關鍵字是由編譯器保留、具有特殊用途的單字。簡單舉例:if。

它們是語言的語法組成部分,編譯器會將它解讀為語法指令,所以不能作為變數、函數或其他識別符的名稱

目前依照 C 語言版本不同有不同數量的保留關鍵字, ANSI C 標準中 32 個保留關鍵字、到 C99 有 37 個、C11 有 44 個。

下面依照功能不同列舉,黑色是 ANSI C 標準,藍色為 C99 增加的,紫色則是 C11 增加的,粗體為有額外說明。部分面試常見、我自己有些心得的保留字會有額外的筆記。

 

流程控制關鍵字

if          else        switch     case        default
for        while      do           break       continue
goto     return


資料型態關鍵字

void      char            short             int         long
float      double        signed          unsigned
_Bool    _Complex   _Imaginary
struct    union         enum

unsigned:常見陷阱

  • 有號數(int)與 unsigned 比較或運算時,有號數會先轉成 unsigned

    int x = -1;
    unsigned int y = 1;
    (x + y > 0) ? puts(">0"):puts("<=0");

    這邊 x 的值會被強制轉成 1,所以結果會輸出「>0」

  • unsigned 變數小於 0 的判斷永遠為假,因為它不能為負數

    for (unsigned int i = 10; i >= 0; i--)

    這會變成無限迴圈

  • overfloat 時,會循環

    unsigned int a = 0;
    a--;

    a 會變成最大值(例如 4294967295) 

union

  • 不同的資料成員共用相同的記憶體位置,會用於對資料空間有限制的系統(如:嵌入式系統、通訊協定的封包、驅動程式)

  • mutithread 、需要追蹤變數的環境適合使用

  • 直接用範例解釋會比較清楚:

    union Data {
        int i;
        float f;
        char str[20];
    };
    
    int main() {
        union Data data;
    
        data.i = 10;
        printf("data.i: %d\n", data.i);
    
        data.f = 3.14;
        printf("data.f: %.2f\n", data.f);
    
        // 設定 f 後,i 的值會被覆蓋(因為共用記憶體)
        printf("data.i (after setting f): %d\n", data.i);
        return 0;
    }

  • 編譯器會根據最大成員大小分配空間,以上面的例子來說,會分配 char str[20]; 的大小

  • 在嵌入式系統上的使用例子:

    union {
      unsigned short TACTL;
      struct {
        unsigned short TAIFG : 1;
        unsigned short TAIE : 1;
        unsigned short TACLR : 1;
        unsigned short : 1;
        unsigned short TAMC : 2;
        unsigned short TAID : 2;
        unsigned short TASSEL : 2;
        unsigned short : 6;
      } TACTL_bit;
    } TimerA;

    當我們需要一口氣抓全部控制訊息時,直接抓 TimerA.TACTL 即可,但當我們需要個別 bit 進行控制時,可針對 TimerA.TACTL_bit 裡的內容進行操控

enum

  • 定義一組具名整數常數的資料型態

  • 以下範例能清楚解釋怎麼定義,如果沒有賦予第一個成員值,便會依序設為 0、1、2……

    enum Color {
        RED,    // 預設為 0
        GREEN,  // 預設為 1
        BLUE    // 預設為 2
    };
    
    enum Status {
        OK = 200,
        NOT_FOUND = 404,
        INTERNAL_ERROR = 500
    };

    如果上面的 RED 設定為 5,GREEN 和 BLUE 則會依序為 5、6

  • 和 #define 的區別: #define 只是簡單的文字替換,沒有型別資訊;enum 有明確的範圍與可讀性,可在 debugger 中顯示名稱而不是純數字

struct

  • 將多個不同型態的變數組合成一個新的資料型態

 

儲存類別關鍵字:決定變數生命週期與可見範圍

auto      static      extern     register
_Thread_local
 

static 的作用

  • 修飾區域變數(延長生命週期)
    在 function 宣告的變數通常在離開 function時就會銷毀,但加上 static 後,只要程式執行期間該變數就會一直存在。

    #include <stdio.h>
    void counter() {
        static int count = 0; // 僅初始化一次
        count++;
        printf("count = %d\n", count);
    }
    
    int main() {
        counter(); // count = 1
        counter(); // count = 2
        counter(); // count = 3
    }

  • 修飾全域變數與 function(限制作用範圍)
    限制全域變數與 function 只能在該檔案範圍中使用。


修飾詞關鍵字:描述變數或函式的額外性質 

const       volatile    inline     restrict
_Alignas  _Alignof

const

  • 增加「read-only」的屬性,修飾的對象可能是變數、資料或指標本身。

  • 簡易舉例

    • const int *p:資料不變(指標可變)

    • int *const p:指標不變(資料可變)

    • const int *const p:指標與資料都不變

volatile

  • 這個變數的值可能會被外部因素改變,所以不要進行編譯器最佳化,每次讀取時一定要重新從記憶體抓值。
  • 常用於嵌入式、中斷服務程式 (ISR)、硬體寄存器、與多執行緒環境
  • 如果 const 和 volatile 一起使用會如何?
    不矛盾,可以一起用。這種情況常出現在硬體暫存器、multithreading 和 interrupt 中。範例如下:
    const volatile int flag = 0; // flag 只能被外部修改,主程式只能讀
      void loop() {
          while (flag == 0) {} // 等待 flag 被外部事件改變
    
          // 當 flag 被設為非 0 時,進行某些處理
          printf("Flag is set! Continue...\n");
      }

    假設某個全域變數是由中斷處理程序(ISR)或其他執行緒改變,而主程式只能「監控」這個變數。

inline

  • 作用:建議編譯器將該函式的程式碼「直接插入(展開)」到呼叫的地方,以減少函式呼叫開銷(呼叫/返回),提高效率。
  • 適合用於函式不大、但常被呼叫的類型 
  • 和 #define 的比較
    功能 inline #define
    本質 函式 預處理器指令(文字替換)
    參數處理 有型別檢查、可傳運算式 無型別檢查,僅字串替換
    可除錯 可設定中斷點、可堆疊追蹤 不可除錯(展開後無來源對應)
    運算處理 每參數僅計算一次 容易出錯
    可讀性 函式語法,清楚易讀 可讀性差,尤其巨集複雜時
    建議用在 小函式或性能敏感的邏輯 常數定義
    在運算處理容易出錯、以及傳運算式部分,用例子比較一下
    inline int square(int x) {
        return x * x;
    }
    square(i++);
    #define SQUARE(x) ((x)*(x))
    SQUARE(i++);  // 展開為 ((i++)*(i++)) → 錯誤,i++ 被執行兩次
    


其他關鍵字

sizeof        typedef       _Atomic
_Generic      _Noreturn     _Static_assert

typedef

  • 設定現有型別的別名,在編譯過程中會進行型別檢查

  • 和 #define 的比較

    #define dPS struct s *
    dPS a, b; // a 是 struct s*,b 是 struct s (非指標)
    
    typedef struct s * tPS;
    tPS a, b; // a 和 b 都是 struct s*






留言