标准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.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: 320653941@qq.com
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/局部变量/9844788
[12. 全局变量] https://baike.baidu.com/item/全局变量/4725296?fr=aladdin
[13. const] https://www.geeksforgeeks.org/const-qualifier-in-c/
[14. static] https://blog.csdn.net/qq_26039331/article/details/52749970