C/C++(10)C/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/局部变量/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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值