深入理解include预编译原理
http://ticktick.blog.51cto.com/823160/596179
你了解 #include 某个 .h 文件后,编译器做了哪些操作么? 你清楚为什么在 .h文件中定义函数实现的话需要在函数前面加上 static 修饰么?你知道 #ifndef……#define……#endif 这种防止头文件重复包含的精髓所在么?本文就是来探讨这些问题,并给出我的理解和思考,欢迎大家留言交流。
1. #include 命令的作用
1.1 什么情况不使用 include
- //a.c文件
- void test_a()
- {
- return;
- }
- //b.c文件
- void test_a(); // 函数声明
- void test_b()
- {
- test_a(); // 由于上面已经声明了,所以可以使用
- }
其实,这样的工程,可以不用使用 include 预编译命令。
1.2 什么情况使用 include
如果工程里面的函数特别多,那么按照上面的做法,则必须在每一个 .c 文件的开头列出所有本文件调用过的函数的声明,这样很不高效,而且一旦某个函数的形式发生变化,又得一个一个改 .c 开头的函数声明。
因此,#include 预编译命令诞生。
- //a.c文件
- void test_a()
- {
- return;
- }
- //a.h文件
- void test_a();
- //b.c文件
- #include "a.h" // 包含含有 test_a() 函数声明的头文件
- void test_b()
- {
- test_a();
- }
1.3 #include 起到什么效果
上述代码在编译器进行预编译的时候,遇到 #include "a.h" ,则会把整个 a.h 文件都copy到 b.c 的开头,就是复制,因此,在实际编译 b.c 之前,b.c 已经被修改为了如下形式:
- //b.c 预编译后的临时文件
- void test_a();
- void test_b()
- {
- test_a();
- }
由此可见,得到的效果和手动加 test_a() 函数声明时的效果相同。
#tips# 在Linux下,可以使用 gcc -E b.c 来查看预编译 b.c 后的效果。
2. static 关键词的使用
2.1 什么叫函数重复定义
我们经常会遇到报错,说变量或者函数重复定义。那么,在此,首先我举例说明一下什么叫函数的重复定义。
- //a.c文件
- void test()
- {
- return;
- }
- //b.c文件
- void test()
- {
- return;
- }
那么,在编译的时候是不会报错的,但是,在链接的时候,会出现报错:
multiple definition of `test',因为在同一个工程里面出现了两个test函数的定义。
2.2 在.h里面写函数实现
如果在 .h 里面写了函数实现,会出现什么情况?
- //a.h文件
- void test_a()
- {
- return;
- }
- //b.c文件
- #include "a.h"
- void test_b()
- {
- test_a();
- }
预编译后,会发现,b.c 被修改为如下形式:
- //b.c 预编译后的临时文件
- void test_a()
- {
- return;
- }
- void test_b()
- {
- test_a();
- }
当然,这样目前是没有什么问题的,可以正常编译链接成功。但是,如果有一个 c.c 也包含的 a.h 的话,怎么办?
- //c.c文件
- #include "a.h"
- void test_c()
- {
- test_a();
- }
同上,c.c 在预编译后,也形成了如下代码:
- // c.c 预编译后的临时文件
- void test_a()
- {
- return;
- }
- void test_c()
- {
- test_a();
- }
那么,在链接器进行链接(link)的时候,会报错:
multiple definition of `test_a'
因此,在 .h 里面写函数实现的弊端就暴露出来了。但是,经常会有这样的需求,将一个函数设置为 内联(inline) 函数,并且放在 .h 文件里面,那么,怎样才能防止出现上述 重复定义的报错呢?
2.3 static 关键词
应对上面的情况,static关键词很好地解决了这个问题。
用static修饰函数,则表明该函数只能在本文件中使用,因此,当不同的文件中有相同的函数名被static修饰时,不会产生重复定义的报错。例如:
- //a.c文件
- static void test()
- {
- return;
- }
- void test_a()
- {
- test();
- }
- //b.c文件
- static void test()
- {
- return;
- }
- void test_b()
- {
- test();
- }
编译工程时不会报错,但是test()函数只能被 a.c 和 b.c 中的函数调用,不能被 c.c 等其他文件中的函数调用。
那么,用static修饰 .h 文件中定义的函数,会有什么效果呢?
- //a.h文件
- static void test()
- {
- return;
- }
- //b.c文件
- #include "a.h"
- void test_b()
- {
- test();
- }
- //c.c文件
- #include "a.h"
- void test_c()
- {
- test();
- }
这样的话,在预编译后,b.c 和 c.c 文件中,由于 #include "a.h" ,故在这两个文件开头都会定义 static void test() 函数,因此,test_b() 和 test_c() 均调用的是自己文件中的 static void test() 函数 , 因此不会产生重复定义的报错。
因此,结论,在 .h 文件中定义函数的话,建议一定要加上 static 关键词修饰,这样,在被多个文件包含时,才不会产生重复定义的错误。
3. 防止头文件重复包含
经常写程序的人都知道,我们在写 .h 文件的时候,一般都会加上
- #ifndef XXX
- #define XXX
- ……
- #endif
这样做的目的是为了防止头文件的重复包含,具体是什么意思呢?
它不是为了防止多个文件包含某一个头文件,而是为了防止一个头文件被同一个文件包含多次。具体说明如下:
- //a.h文件
- static void test_a()
- {
- return;
- }
- //b.c文件
- #include "a.h"
- void test_b()
- {
- test_a();
- }
- //c.c
- #include "a.h"
- void test_c()
- {
- test_a();
- }
这样是没有问题的,但下面这种情况就会有问题。
- //a.h文件
- static void test_a()
- {
- return;
- }
- //b.h文件
- #include "a.h"
- //c.h文件
- #include "a.h"
- //main.c文件
- #include "b.h"
- #include "c.h"
- void main()
- {
- test_a();
- }
这样就“不小心”产生问题了,因为 b.h 和 c.h 都包含了 a.h,那么,在预编译main.c 文件的时候,会展开为如下形式:
- //main.c 预编译之后的临时文件
- static void test_a()
- {
- return;
- }
- static void test_a()
- {
- return;
- }
- void main()
- {
- test_a();
- }
在同一个 .c 里面,出现了两次 test_a() 的定义,因此,会出现重复定义的报错。
但是,如果在 a.h 里面加上了 #ifndef……#define……#endif 的话,就不会出现这个问题了。
例如,上面的 a.h 改为:
- //a.h 文件
- #ifndef A_H
- #define A_H
- static void test_a()
- {
- return;
- }
- #endif
预编译展开main.c则会出现:
- //main.c 预编译后的临时文件
- #ifndef A_H
- #define A_H
- static void test_a()
- {
- return;
- }
- #endif
- #ifndef A_H
- #define A_H
- static void test_a()
- {
- return;
- }
- #endif
- void main()
- {
- test_a();
- }
在编译main.c时,当遇到第二个 #ifndef A_H ,由于前面已经定义过 A_H,故此段代码被跳过不编译,因此,不会产生重复定义的报错。这就是 #ifndef……#define……#endif 的精髓所在。
本文出自 “对影成三人” 博客,请务必保留此出处http://ticktick.blog.51cto.com/823160/596179
http://blog.chinaunix.net/uid-26435987-id-3077444.html
#define ....
#define ....
....
....
#define a 100
....
此时,我们要检查a是否定义(假设我们已经记不着这点了),或者我们要给a一个不同的值,就加入如下句子
#if defined a
#undef a
#define a 200
#endif
上述语句检验a是否被定义,如果被定义,则用#undef语句解除定义,并重新定义a为200
同样,检验a是否定义:
#ifndef a //如果a没有被定义
#define a 100
#endif
以上所用的宏中:#undef为解除定义,#ifndef是if not defined的缩写,即如果没有定义。
这就是#if defined 的唯一作用!
#if defined XXX_XXX
#endif
是条件编译,是根据你是否定义了XXX_XXX这个宏,而使用不同的代码。
一般.h文件里最外层的
#if !defined XXX_XXX
#define XXX_XXX
#endif
是为了防止这个.h头文件被重复include。
2)
#error XXXX
是用来产生编译时错误信息XXXX的,一般用在预处理过程中;
例子:
#if !defined(__cplusplus)
#error C++ compiler required.
#endif
有一道经典的C语言问题,关于宏定义中#和##符号的使用和宏定义展开问题
程序如下:
#include <stdio.h>
#define f(a,b) a##b
#define g(a) #a
#define h(a) g(a)
int main()
{
printf("%s\n", h(f(1,2)));
printf("%s\n", g(f(1,2)));
return 0;
}
答案:第一行:12 第二行:f(1,2)
说明:
1、关于符号#和##
两个符号都只能用于预处理宏扩展。不能在普通的源码中使用它们,只能在宏定义中使用。
简单的说,#是把宏参数变为一个字符串,##是把两个宏参数连接在一起。
关于这两个符号的具体意义和用法可以参见两篇文章:
#和##在宏替换中的作用 http://www.linuxidc.com/Linux/2014-06/102921.htm
C/C++ 宏中"#"和"##"的用法 http://www.linuxidc.com/Linux/2014-06/102924.htm
还有GCC帮助文档上的解释:
3.4 Stringification
3.5 Concatenation
2、关于宏展开
预处理过程的几个步骤:
1)字符集转换(如三联字符)
2)断行链接/
3)注释处理,/* comment */,被替换成空格
4)执行预处理命令,如#inlcude、#define、#pragma、#error等
5)转义字符替换
6)相邻字符串拼接
7)将预处理记号替换为词法记号
第4)步即如何展开宏函数的规则:在展开当前宏函数时,如果形参有#或##则不进行宏参数的展开,否则先展开宏参数,再展开当前宏。
宏替换顺序英文描述如下:
A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token, is replaced by the corresponding argument after all macros contained therein have been expanded.
3、总结
综合以上,对于这道题来说,第一行h(f(1,2)),由于h(a)非#或者##所以先展开其参数f(1,2),即12,所以变成h(12),然后再宏替换为g(12),再次替换为12。
第二行g(f(1,2)),宏g(a)带有#,所以里面的f(1,2)不展开,所以变成f(1,2)
类似的这种问题在《你必须知道的495个C语言问题》中出现过,在121页的“预处理功能”的问题11.19,有兴趣的朋友可以看一看。
深入理解include预编译原理的更多相关文章
- JavaScript预编译原理分析
一直对变量对象,活动对象,预编译,变量提升,执行上下文的时间顺序有着凌乱的认识,但是这些对理解JS语法有着很重要的作用.读了很多人的文章,都没有一个特别清晰的把这些写出来. 今天主要总结一下现阶段自己 ...
- 浅谈JavaScript预编译原理
这两天又把js的基础重新复习了一下,很多不懂得还是得回归基础,大家都知道js是解释性语言,就是编译一行执行一行,但是在执行的之前,系统会做一些工作: 1,语法分析: 2,预编译: 3,解释执行. 语法 ...
- 深入理解 Flutter 的编译原理与优化
阿里妹导读:对于开发者而言,Flutter工程和我们的Android/iOS工程有何差别?Flutter的渲染和事件传递机制如何工作?构建缓慢或出错又如何去定位,修改和生效呢?凡此种种,都需要对Flu ...
- 带你玩转Visual Studio——带你理解微软的预编译头技术
原文地址:http://blog.csdn.net/luoweifu/article/details/49010627 不陌生的stdafx.h 还记得带你玩转Visual Studio——带你新建一 ...
- atitit.查看预编译sql问号 本质and原理and查看原生sql语句
atitit.查看预编译sql问号 本质and原理and查看原生sql语句 1. 预编译原理. 1 2. preparedStatement 有三大优点: 1 3. How to look gene ...
- GCC编译器原理(三)------编译原理三:编译过程---预处理
Gcc的编译流程分为了四个步骤: 预处理,生成预编译文件(.文件):gcc –E hello.c –o hello.i 编译,生成汇编代码(.s文件):gcc –S hello.i –o hello. ...
- gcc——预处理(预编译),编译,汇编,链接
一,预编译 操作步骤:gcc -E hello.c -o hello.i 主要作用: 处理关于 “#” 的指令 [1]删除#define,展开所有宏定义.例#define portnumber 333 ...
- 预编译头文件pch
1. 预编译头文件 作用:提高编译效率.预编译头文件(扩展名为.PCH),是为了提高编译效率而使用的一种方法,把一个工程中较稳定的代码预先编译好放在一个文件(.PCH)里.避免每次编译 ...
- JavaScript之预编译
javascript是一种解释性弱类型语言,在浏览器中执行时,浏览器会先预览某段代码进行语法分析,检查语法的正确与否,然后再进行预编译,到最后才会从上往下一句一句开始执行这段代码,简单得来说可以表示为 ...
随机推荐
- PostgreSQL Monitor pg_activity
PostgreSQL Monitor pg_activity Command line tool for PostgreSQL server activity monitoring. https:// ...
- PostgreSQL Replication之第十章 配置Slony(4)
10.4 部署DDLs 对于生产性的应用程序来说,仅仅复制一个表明显是不够的.此外,通过没有办法保证数据从来不会发生改变.在某些时候,部署变化的数据结构(所谓的DDLs)是必要的. 现在的问题是,Sl ...
- IOS Suppot Font 苹果默认支持的字体一览2(普通,加粗,倾斜)
- MAC开发NDK非常的简单
转自:http://www.cnblogs.com/jarrah/archive/2013/03/15/2961892.html 附带CDT的下载:http://www.eclipse.org/cdt ...
- jquery ajax 个人总结
jquery : 在获取对象的时候,不要用dem的与jquery的混合写法,有的时候 用js获取到的对象 没有JQUERY对应的方法 会报一些不知道的错误.(即如果要使用jquery 就使用jque ...
- JSP和servlet有什么区别?
JSP和servlet有什么区别? JSP是Servlet技术的扩展,本质上是Servlet的简易方式,更强调应用的外表表达. JSP编译后是"类servlet". Servlet ...
- 通过反射封装JDBC
具体上代码我的BaseDao: public class BaseDao<T> { private Class clazz; private Properties pro=null; ...
- CCF真题之节日
201503-3 问题描述 有一类节日的日期并不是固定的,而是以“a月的第b个星期c”的形式定下来的,比如说母亲节就定为每年的五月的第二个星期日. 现在,给你a,b,c和y1, y2(1850 ≤ y ...
- oracle文件版本
strings -a $AU_TOP/forms/US/GLXFCRVL.fmb|grep '$Header' 比如 strings -a /u02/CRP2/apps/apps_st/appl/a ...
- 夺命雷公狗ThinkPHP项目之----企业网站6之栏目的添加(主要用模型来验证字段)
我们刚才的控制器已经写好了,那么我们现在就来完成我们的模型, 首先我们在Model目录下创建一个CategoryModel.class.php 代码如下: <?php namespace Adm ...