1. 程式人生 > >標準C頭文注意事項

標準C頭文注意事項

標準C標頭檔案

本文介紹標準C標頭檔案以及一些注意事項。

1. 包含一個頭檔案,即包含該標頭檔案的所有標頭檔案

標頭檔案在預編譯時候處理,因此,對於當前檔案本地變數放到 .c 檔案,對於全域性變數放到 .h 檔案,換句話說,標頭檔案中 #include 或者 #define 的,只要引用該標頭檔案一次,

  • Case 1.0. 包含某個標頭檔案,即包含該標頭檔案的所有標頭檔案,但並不意味著包含該標頭檔案的所有.c具體實現,後者可以通過 makefile 實現,這是後話。

    printa.h 的原始碼如下,

    
    #ifndef printa_h
    
    
    #define printa_h
    
    
    
    #include <stdio.h>
    
    
    void printa();
    
    
    #endif
    

    printa.c

    的原始碼如下,

    
    #include "printa.h"
    
    
    void main()
    {
     printa();
    }
    
    void printa()
    {
    printf("a\n");
    }

    printb.h 包含 printa.h ,也遞迴相應地包含printa.h 中的 stdio.h,程式碼如下,

    
    #ifndef printb_h
    
    
    #define printb_h
    
    
    void printb();
    
    
    #endif
    

    printb.c 的原始碼如下,

    
    #include "printb.h"
    
    
    #include "printa.h"
    
    
    void main()
    {
     printb();
    }
    
    void
    printb() { printf("b\n"); }

    編譯成功,gcc version 5.4.0 20160609 (Ubuntu5.4.0-6Ubuntu1~16.04.10)

    ~$ gcc printb.c -o printb

    ~$ ls

    printb.c printb

    ~$ ./printb

    b

  • Case 1.1. 兩個標頭檔案定義相同的函式,預編譯時出現重定義錯誤

    printa.h 的原始碼如下,

    
    #ifndef printa_h
    
    
    #define printa_h
    
    
    
    #include <stdio.h>
    
    
    void printa();
    
    
    #endif
    

    printa.c 的原始碼如下,

    
    #include "printa.h"
    
    
    void printa()
    {
    printf("a\n");
    }

    printb.h 重定義 printa.hprinta() 函式,原始碼如下,

    
    #ifndef printb_h
    
    
    #define printb_h
    
    
    void printa();
    
    
    #endif
    

    printb.c 的原始碼如下,

    
    #include "printb.h"
    
    
    #include "printa.h"
    
    
    #include "printa.h"
    
    
    void main()
    {
     printa();
    }
    
    void printa()
    {
    printf("b\n");
    }

    預編譯報錯,錯因重定義 printa() 函式 gcc version 5.4.0 20160609 (Ubuntu5.4.0-6Ubuntu1~16.04.10)

    ~$ gcc printb.c printa.c -o printb

    ‘tmp/ccQvgeWh.c: In function ‘printa’:

    printa.c: (.text+0x0): multiple definition of ‘printa’

2. “#ifndef” 作為程式碼防護層

使用 #ifndef的目的是避免出現重定義錯誤 [2],同樣地,舉例論證其重要性。

  • Case 2.0. 重複使用相同的檔案導致重定義錯誤。這是因為在沒有額外干預/定義的前提下,編譯器預設不同的路徑的檔案是不一樣的,即使它們檔名相同../print/chhildDic 目錄下存放printx.h,程式碼如下,

    
    #define Y 2
    

    ../print目錄下存放printx.h,程式碼如下,

    
    #define X 1
    

    ../print 目錄下存放printx.c,程式碼如下,

    
    #include <stdio.h>
    
    
    #include "printx.h"
    
    
    #include "childDic/printx.h"
    
    
    void main()
    {
    printf("X = %d\n", X);
    printf("Y = %d\n", Y);
    }

    編譯成功:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

    ~$ gcc printx.c -o printx

    ~$ ls

    printx.c printx

    ~$ ./printx

    X = 1

    Y = 2

    C編譯器認為兩件事:第一件事:編譯器預設不同的路徑的檔案是不一樣的,即使它們檔名相同,我們已經驗證過了;第二件事:不同的檔案定義相同的變數會出現重定義錯誤——不難推匯出:“相同的檔案放到不同的目錄,同時引用時會出現 重定義的錯誤”,我們接著驗證之。

    ../print/chhildDic 目錄下存放printx.h,程式碼如下

    
    #define X 2
    

    ../print目錄下存放printx.h,程式碼如下,

    
    #define X 1
    

    ../print 目錄下存放printx.c,程式碼如下,

    
    #include <stdio.h>
    
    
    #include "printx.h"
    
    
    #include "childDic/printx.h"
    
    
    void main()
    {
    printf("X = %d\n", X);
    }

    編譯失敗:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

    ~$ gcc printx.c -o printx

    In file included from printx.c:3:0:

    childDic/printx.h:4:0: warning: “X” redefined

  • Case 2.1. 相同的變數定義在不同的檔案導致重定義錯誤。

    printx.h,程式碼如下,

    
    #define X 1
    

    printy.h,程式碼如下,

    
    #define X 2
    

    printx.c,程式碼如下,

    
    #include <stdio.h>
    
    
    #include "printx.h"
    
    
    #include "printy.h"
    
    
    void main()
    {
    printf("%d\n", X);
    }

    編譯失敗:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

    ~$ gcc printx.c -o printx

    In file included from printx.c:3:0:

    childDic/printx.h:4:0: warning: “X” redefined

如何解決上述問題,關於ifndef (以第一次定義的為準,第二次及往後的重定義不算數),下面舉例,

  • Solution 2.0. 建立檔案時候使用ifndef,避免重複建立相同的檔案(以第一次定義的為準,第二次及往後的重定義不算數)../print/chhildDic 目錄下存放printx.h,程式碼如下,

    
    #ifndef printx_h
    
    
    #define printx_h
    
    
    
    #define X 2
    
    
    
    #endif
    

    ../print目錄下存放printx.h,程式碼如下,

    
    #ifndef printx_h
    
    
    #define printx_h
    
    
    
    #define X 1
    
    
    
    #endif
    

    ../print 目錄下存放printx.c,程式碼如下,

    
    #include <stdio.h>
    
    
    #include "childDic/printx.h"
    
    
    #include "printx.h"
    
    
    void main()
    {
    printf("%d\n", X);
    }

    編譯成功:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

    ~$ gcc printx.c -o printx

    ~$ ls

    printx.c printx

    ~$ ./printx

    2

  • Solution 2.1. 建立變數時候使用ifndef,避免重複建立相同的變數(以第一次定義的為準,第二次及往後的重定義不算數)../print/chhildDic 目錄下存放printx.h,程式碼如下,

    
    #ifndef X
    
    
    
    #define X 1
    
    
    
    #endif
    

    ../print目錄下存放printx.h,程式碼如下,

    
    #ifndef X
    
    
    
    #define X 2
    
    
    
    #endif
    

    ../print 目錄下存放printx.c,程式碼如下,

    
    #include <stdio.h>
    
    
    #include "childDic/printx.h"
    
    
    #include "printx.h"
    
    
    void main()
    {
    printf("%d\n", X);
    }

3. “extern”和全域性變數的區別

全域性變數:全域性變數相對於區域性變數而言。顧名思義,只能被函式內部訪問 (比如:分配記憶體空間,變數定義,變數賦值) 的變數稱為區域性變數 [11];能夠被所有函式訪問 (比如:分配空間,變數定義,變數賦值) 的變數稱為全域性變數 [12]。

“extern”: 該關鍵字的意義在於宣告而不分配其 (比如:變數,函式) 記憶體空間,不定義,不賦初始值 [2-7]。從定義出發。”extern” 的關鍵字使用方式:先分配記憶體空間定義之,後賦值使用,即:定義在全域性,”extern” 就有了全部變數的效果,定義在函式內部,”extern” 就有了區域性變數的效果 [9]。舉個例子,

printa.c,程式碼如下,

#include <stdio.h>

extern int var;

void f1()
{
    int var = 1;
    printf("%d\n", var);
}

void f2()
{
    printf("%d\n", var);
}


int main(void)
{
    f1();
    f2();

    return 0;
}

編譯失敗:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

In function ‘f2’:

printa.c: undefined reference to ‘var’

#include <stdio.h>

extern int var;

void f1()
{
    int var = 1;
    printf("%d\n", var);
}

void f2()
{
    int var = 2;
    printf("%d\n", var);
}


int main(void)
{
    f1();
    f2();

    return 0;
}

編譯成功:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

~$ gcc printa.c -o main

~$ ls

printa.c main

~$ ./main

1

2

Question 3.0. 為什麼使用”extern”?

Sulution 3.0. extern 解決了全域性變數的一個問題:多個不同的函式讀取同一個全域性變數,這多個函式讀取的是相同的值;多個函式定義相同的extern,這多個函式定義的是不同的值。這是有不同的應用場景的:比如AES有一個論函式Nr變數,Nr可以為9輪,可以為14輪等,雖然都叫Nr。”extern” 的特性有它特定的應用場景。

4. “static”不放在標頭檔案中

static又稱靜態區域性變數。靜態區域性變數和普通區域性變數最大的區別在於:靜態區域性變數只在宣告處(函式或檔案內)可見,且每次呼叫區域性值仍然保留![8, 14]。通常建議static不要放在標頭檔案,而是放在原始檔中。針對“static區域性值在可見出每次呼叫後保留”,我們舉個例子,

printx.c,程式碼如下,

#include <stdio.h>

int fun()
{
  static int a = 0;
  a++;
  return a;
} 

int fun2()
{
  int a = 0;
  a++;
  return a;
} 

int main()
{
  printf("After one operation, value of static variable: %d \n", fun());
  printf("After two operations, valur of static variable: %d \n", fun());
  printf("After one operation, value of common local variable: %d \n", fun2());
  printf("After two operations, value of common local variable: %d \n", fun2());
  return 0;
}

編譯成功:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

~$ gcc printx.c -o main

~$ ./main

After one operation, value of static variable: 1

After two operations, valur of static variable: 2

After one operation, value of common local variable: 1

After two operations, value of common local variable: 1

5. “const”之只讀變數

const關鍵字宣告該變數為read only變數 [13], 我們舉個例子,

printx.c,程式碼如下,

#include <stdio.h>

int main()
{
  const int a = 1;
  printf("Initial value a: %d \n", a);
  a = 2;
  printf("After modification a: %d \n", a);

  return 0;
}

編譯失敗:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

printx.c: In function ‘main’:

printx.c:7:5: error: assignment of read-only variable ‘a’

6. And More ?

For more questions or feedbacks, please contact me by:

References

[1. once-only headers] https://www.tutorialspoint.com/cprogramming/c_header_files.htm

[2. extern] https://jameshfisher.com/2017/08/28/c-extern-function.html

[3. extern] https://stackoverflow.com/questions/19198855/why-use-the-extern-keyword-in-header-in-c

[4. tenative definition] https://stackoverflow.com/questions/3095861/about-tentative-definition

[5. tenative definition and external] https://en.cppreference.com/w/c/language/extern

[6. tenative definition and external] https://www.quora.com/What-is-the-tentative-definition-of-a-global-variable-in-C

[7. tenative definition and external] https://en.cppreference.com/w/c/language/extern#Tentative_definitions

[8. static] https://www.geeksforgeeks.org/static-variables-in-c/

[9. extern] https://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

[10. C complier] https://ide.geeksforgeeks.org/index.php

[11. 區域性變數] https://baike.baidu.com/item/%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F/9844788

[12. 全域性變數] https://baike.baidu.com/item/%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F/4725296?fr=aladdin

[13. const] https://www.geeksforgeeks.org/const-qualifier-in-c/

[14. static] https://blog.csdn.net/qq_26039331/article/details/52749970