C语言的本质(20)——预处理之二:条件预处理和包含头文件
我们可以通过定义不同的宏来决定编译程序对哪些代码进行处理。条件编译指令将决定那些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。
条件编译可分为三种情况,按照不同的条件去编译不同的程序部分,因而产生不同的目标文件,这对于程序的移植和调试都非常有用。
1、
#ifdef 标识符
程序段1
#else
程序段2
#endif
功能:如果标识符已经被#define定义过,则对程序段1进行编译,否则对程序段2进行编译。如果没有程序断2,#else可以没哟。
2、
#ifndef 标识符
程序段1
#else
程序段2
#endif
它的功能是跟上面恰恰相反的,也就是说如果标识符没有被#define定义过,则对程序段1进行编译,否则对程序段2进行编译。
3、
#if 常量表达式
程序段1;
#else
程序段2;
#endif
功能:根据常量表达式的真假去判断执行哪个程序段,如果为真则执行程序段1,否则执行程序段2。
条件预处理可以用来调试程序,比如:
#define DEBUG //此时#ifdefDEBUG为真
//#define DEBUG 0 //此时为假
int main(void)
{
#ifdef DEBUG
printf("Debugging\n");
#else
printf("Notdebugging\n");
#endif
printf("Running\n");
return0;
}
这样我们就可以实现debug功能,每次要输出调试信息前,只需要#ifdefDEBUG判断一次。不需要了就在文件开始定义#define DEBUG
条件预处理指示常用于源代码的配置管理,例如:
#if MACHINE == arm
int x;
#elif MACHINE == x64
long x;
#else /* all others */
#error UNKNOWN TARGET MACHINE
#endif
假设这段程序是为多种平台编写的,在arm平台上需要定义x为int型,在x64平台上需要定义x为long型,对其它平台暂不提供支持,就可以用条件预处理指示来写。如果在预处理这段代码之前,MACHINE被定义为arm,则包含intx;这段代码;否则如果MACHINE被定义为x64,则包含long x;这段代码;否则(MACHINE没有定义,或者定义为其它值),包含#error UNKNOWN TARGET MACHINE这段代码,编译器遇到这个预处理指示就报错退出,错误信息就是UNKNOWN TARGET MACHINE。
如果要为x64平台编译这段代码,有几种可选的办法:
1、手动编辑代码,在前面添一行#defineMACHINE x64。这样做的缺点是难以管理,如果这个项目中有很多源文件都需要定义MACHINE,每次要为x64平台编译就得把这些定义全部改成x64,每次要为arm平台编译就得把这些定义全部改成arm。
2、在所有需要配置的源文件开头包含一个头文件,在头文件中定义#define MACHINE x64,这样只需要改一个头文件就可以影响所有包含它的源文件。通常这个头文件由配置工具生成,比如在Linux内核源代码的目录下运行make menuconfig命令可以出来一个配置菜单,在其中配置的选项会自动转换成头文件include/linux/autoconf.h中的宏定义。
举一个具体的例子,在内核配置菜单中用回车键和方向键进入Device Drivers ---> Network device support,然后用空格键选中Networkdevice support(菜单项左边的[ ]括号内会出现一个*号),然后保存退出,会生成一个名为.config的隐藏文件,其内容类似于:
......
#
# Network device support
#
CONFIG_NETDEVICES=y
# CONFIG_DUMMY is not set
# CONFIG_BONDING is not set
# CONFIG_EQUALIZER is not set
# CONFIG_TUN is not set
......
然后运行make命令编译内核,这时根据.config文件生成头文件include/linux/autoconf.h,其内容类似于:
......
/*
*Network device support
*/
#define CONFIG_NETDEVICES 1
#undef CONFIG_DUMMY
#undef CONFIG_BONDING
#undef CONFIG_EQUALIZER
#undef CONFIG_TUN
......
上面的代码用#undef确保取消一些宏的定义,如果先前没有定义过CONFIG_DUMMY,用#undef CONFIG_DUMMY取消它的定义没有任何作用,也不算错。
include/linux/autoconf.h被另一个头文件include/linux/config.h所包含,通常内核代码包含后一个头文件,例如net/core/sock.c:
......
#include <linux/config.h>
......
int sock_setsockopt(struct socket *sock,int level, int optname,
char __user *optval, intoptlen)
{
......
#ifdef CONFIG_NETDEVICES
case SO_BINDTODEVICE:
{
......
}
#endif
......
再比如drivers/isdn/i4l/isdn_common.c:
......
#include <linux/config.h>
......
static int
isdn_ioctl(struct inode *inode, struct file*file, uint cmd, ulong arg)
{
......
#ifdef CONFIG_NETDEVICES
case IIOCNETGPN:
/* Get peerphone number of a connected
* isdn networkinterface */
if (arg) {
if (copy_from_user(&phone, argp, sizeof(phone)))
return -EFAULT;
return isdn_net_getpeer(&phone, argp);
} else
return -EINVAL;
#endif
......
#ifdef CONFIG_NETDEVICES
case IIOCNETAIF:
......
#endif /* CONFIG_NETDEVICES */
......
这样,在配置菜单中所做的配置通过条件预处理最终决定了哪些代码被编译到内核中。#ifdef或#if可以嵌套使用,但预处理指示通常都顶头写不缩进,为了区分嵌套的层次,可以像上面的代码中最后一行那样,在#endif处用注释写清楚它结束的是哪个#if或#ifdef。
3、要定义一个宏不一定在代码中用#define定义,比如我们可以用gcc的-D选项定义一个宏NDEBUG。对于上面的例子,我们需要给MACHINE定义一个值,可以写成类似这样的命令:gcc -c -DMACHINE=x64 main.c。这种办法需要给每个编译命令都加上适当的选项,和第2种方法相比似乎也很麻烦,第2种方法在头文件中只写一次宏定义就可以在很多源文件中生效,第3种方法能不能做到“只写一次到处生效”呢?等以后学习了Makefile就有办法了。
最后通过下面的例子说一下#if后面的表达式:
#define VERSION 2
#if defined x || y || VERSION < 3
首先处理defined运算符,defined运算符一般用作表达式中的一部分,如果单独使用,#if defined x相当于#ifdef x,而#if !defined x相当于#ifndef x。在这个例子中,如果x这个宏有定义,则把defined x替换为1,否则替换为0,因此变成#if 0 || y || VERSION < 3。
然后把有定义的宏展开,变成#if 0 || y || 2 < 3。
把没有定义的宏替换成0,变成#if 0 || 0 || 2 < 3,注意,即使前面定义了一个变量名是y,在这一步也还是替换成0,因为#if的表达式必须在编译时求值,其中包含的名字只能是宏定义。
把得到的表达式0 || 0 || 2 < 3像C表达式一样求值,求值的结果是#if 1,因此条件成立。
文件包含
文件包含是C预处理程序的另一个重要功能。文件包含命令行的一般形式为:
#include "文件名"
我们曾经多次用此命令包含过库函数的头文件。例如:
#include "stdio.h"
#include "math.h"
文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件
在程序设计中,文件包含是很有用的。一个大的程序可以分为多个模块,由多个程序员分别编程。有些公用的符号常量或宏定义等可单独组成一个文件,在其它文件的开头用包含命令包含该文件即可使用。这样,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错。
对文件包含命令还要说明以下几点:
包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来。例如以下写法都是允许的:
#include "stdio.h"
#include <math.h>
但是这两种形式是有区别的:
使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;
使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。用户编程时可根据自己文件所在的目录来选择某一种命令形式。
一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。
文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
C语言的本质(20)——预处理之二:条件预处理和包含头文件的更多相关文章
- C语言包含头文件时用引号和尖括号的区别
用尖括号 #include <>: 一般用于包含标准的库头文件,编译器会去系统配置的库环境变量和者用户配置的路径去搜索,而不会在项目的当前目录去查找 用双引号 #include &q ...
- Linux学习---条件预处理的应用
预处理的使用: ⑴包含头文件 #include ⑵宏定义 #define 替换,不进行语法检查 ①常量宏定义:#define 宏名 (宏体) (加括号为防止不进行语法检查而出现的错误) eg:# ...
- C语言的本质(23)——C标准库之输入与输出(上)
1..文件的基本概念 所谓"文件"是指一组相关数据的有序集合.这个数据集有一个名称,叫做文件名.实际上在前面的各章中我们已经多次使用了文件,例如源程序文件.目标文件.可执行文件.库 ...
- C预编译, 预处理, C/C++头文件, 编译控制,
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的 ...
- c语言头文件和源文件不在同一个目录
http://www.cnblogs.com/ShaneZhang/archive/2013/05/20/3088688.html 从工程上讲,头文件的文件名应该与对应的源文件名相同便于维护,如果头文 ...
- 为什么C语言会有头文件
前段时间一个刚转到C语言的同事问我,为什么C会多一个头文件,而不是像Java和Python那样所有的代码都在源文件中.我当时回答的是C是静态语言很多东西都是需要事先定义的,所以按照惯例我们是将所有的定 ...
- C语言头文件、库文件的查找路径
在 程序设计中,文件包含是很有用的.一个大的程序可以分为多个模块,由多个程序员分别编程.有些公用的符号常量或宏定义等可单独组成一个文件,在其它文件的开头用包含命令包含该文件即可使用.这样,可避免在每个 ...
- C++头文件,预处理详解
C++遵循先定义,后使用的原则.就拿函数的使用来举例吧. 我看过有些人喜欢这样写函数. #include<iostream> using namespace std; int add(in ...
- C语言的本质(19)——预处理之一:宏定义
我们在写代码时已多次使用过以"#"号开头的预处理命令.如包含命令#include,宏定义命令#define等.在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面,它们称为 ...
随机推荐
- 信号量 <第六篇>
一.ManualResetEvent 该对象有两种信号量状态True和False.构造函数设置初始状态. WaitOne:该方法用于阻塞线程,默认是无限期的阻塞,支持超时阻塞,如果超时就放弃阻塞,这样 ...
- cf479E Riding in a Lift
E. Riding in a Lift time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- 【C语言用法】C语言的函数“重载”
由于平时很少用到__attribute__定义函数或者变量的符号属性,所以很难想象C语言可以向C++一样进行函数或者变量的重载. 首先,复习一下有关强符号与弱符号的概念和编译器对强弱符号的处理规则: ...
- java POI读取excel 2007/2003
2003版office excel读取 import java.io.FileNotFoundException; import java.io.IOException; import java.io ...
- 带你走近AngularJS - 创建自己定义指令
带你走近AngularJS系列: 带你走近AngularJS - 基本功能介绍 带你走近AngularJS - 体验指令实例 带你走近AngularJS - 创建自己定义指令 ------------ ...
- HDU 4521 间隔》=1的LIS 线段树+dp
九野的博客,转载请注明出处:http://blog.csdn.net/acmmmm/article/details/11991119 题意: n个数 d个距离 下面n个数的序列,求序列中的最长单调递增 ...
- ZOJ Monthly, June 2014 月赛BCDEFGH题题解
比赛链接:点击打开链接 上来先搞了f.c,,然后发现状态不正确,一下午都是脑洞大开,, 无脑wa,无脑ce...一样的错犯2次.. 硬着头皮搞了几发,最后20分钟码了一下G,不知道为什么把1直接当成不 ...
- C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域
面向对象编程 --继承情况下的类作用域 引言: 在继承情况下,派生类的作用域嵌套在基类作用域中:假设不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义. 正是这样的类作用域的层次嵌套使 ...
- 数据文件、日志文件、归档文件、控制文件、参数文件及RMAN备份数据库信息查询
一.查看数据库信息:=====================1.数据文件 SQL> SELECT FILE#,STATUS,ENABLED,NAME FROM V$DATAFILE; FILE ...
- 【高精度+DP】【HDU1223】 OrderCount
Order Count Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Tota ...