標準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
編譯成功,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.h
的printa()
函式,原始碼如下,#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:
- CSDN Home Page: https://blog.csdn.net/Canhui_WANG
- Email: <[email protected]>
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