【C语言讲义更新】C语言中的其它预处理命令
文/Edward
除了上面的三种主要预处理命令之外,C语言中还提供其他的一些预处理命令功能,这些其它的预处理命令可能平时我们做C语言代码的时候很少会用到,但是如果阅读Linux源码,或者其他一些开源项目的时候会经常出现。因此,我觉得了解和理解它们还是必须的,本小节内容将会详细展示如何来使用这些预处理命令。
1 取消符号定义#undef
当一个符号被定义之后,如果我们后面的代码想要改变这个标号的值,那么如果重新定义的话,编译器势必会抛出一些异常。如图1所示。取消符号定义#undef。
图1 重复定义符号出错
那么碰到这种问题的时候该如何来修改之前定义好的符号的值呢?做法很简单,就是先将之前定义的符号取消,然后再重新去定义这个符号的值即可。这里我们就是用#undef预处理命令来完成取消符号的定义。
#undef预处理的用法为:
#undef 符号名称
那么对于图1中的应用,我们可以写出代码如图2所示。
图2# undef预处理取消符号的定义
2 错误输出#error
有时候我们在做一个项目的时候,往往要和别人一起开发一份代码,而自己只是负责某一小块代码的编写和调试。而我们的代码需要给别人去调用。然而此时问题可能恰恰就会出现了,比如当别人调用你代码的时候,传入的参数超界了,但是此时语法没有错误,编译器是没有办法识别出这个超界的错误的。那么有没有一种方法可以指挥编译器根据自己的要求抛出异常呢?答案是有的,那就是使用#error预处理。
#error预处理的用法很简单,如下所示:
#error 错误文本信息
比如,我们写了函数,这个子函数实现的功能为:根据使用时,define定义的符号来实现相应的功能,我们这里就直接使用printf函数打印出不同的信息来替代这些功能。假设这个子函数目前只接收三种类型的符号定义,一旦超出或者没有定义就抛出错误。此时代码如图3所示。
图3 error抛出错误
对于图3中的代码,我们只需要加入这三个选项中某一个或者某几个的定义即可让代码正常编译下去,如图4所示。
图4 代码正常编译
3 命令行定义
有时候,我们想要让C语言程序中定义的符号的具体值变成可选项,然后编译的时候传入进去。比如我们在一个程序中有三个代码块,这三个代码块的功能可能是类似的,只不过每次编译的时候需要根据具体的机型选用其对应的代码块,那这个时候我们就可以使用命令行定义传入了。其具体的示例代码如图5所示。
图5 命令行定义
在图5中,我们分别使用#if defined预处理来选择相应的编译选项,而此时我们在代码中确是没有定义这些编译选项相关的任何符号的。在编译的时候,我们将这些符号传入给编译器使其进行编译时候的代码选择。对于类UNIX的编译器,其命令行符号的传入一般是使用“-D符号定义”的形式。
而输出结果显而易见,当我们传入OPTION1符号的时候,代码块1就被编译了,传入OPTION2符号的时候,代码块2就被编译了,不传入符号的时候,就是用共用代码。
4 指定程序行#line
C语言编译器预处理的功能是非常强大的,除了上述一些基本的应用之外,还能支持代码行数的修改。在C语言中有一些默认的符号是编译器默认定义的,如表1所示:
表1标准C语言中定义的标号
符号 |
定义 |
__LINE__ |
当前程序行的行号,表示为十进制整型常量 |
__FILE__ |
当前源文件名,表示字符串型常量 |
__DATE__ |
编译的日历日期,表示为Mmm dd yyyy 形式的字符串常量,Mmm是由asctime()函数产生的 |
__TIME__ |
编译的时间,表示"hh:mm:ss"形式的字符串型常量,是由asctime()函数产生的。 |
__STDC__ |
编辑器为ISO兼容实现时,为十进制整型常量 |
__STDC_VERSION__ |
如果编译器是符合C89,则这个宏的值为19940SL;如果编译器符合C99,则这个宏的值为199901L;否则数值是未定义 |
__STDC_EOBTED__ |
(C99)实现为宿主实现时为1,实现为独立实现为0 |
__STDC_IEC_559__ |
(C99)浮点数实现符合IBC 60559标准时定义为1,否则数值是未定义 |
__STDC_IEC_559_COMPLEX__ |
(C99)复数运算实现符合IBC 60559标准时定义为1,否则数值是未定义 |
__STDC_ISO_10646__ |
(C99)定义为长整型常量,yyyymmL表示wchar_t值符合ISO 10646标准及其指定年月的修订补充,否则数值未定义 |
除了这些标准的C语言定义的符号之外,gcc针对这些符号还有一些扩展。gcc还支持“__func__”(一定要小写)和“__FUNCTION__”它指示所在的函数。
这一小节中#line预处理命令,正是对应着__LINE__标号。现在,我们可以先使用__LINE__标号打印出当前的行号。如图6所示。
图6 打印出当前行号
而使用了#line预处理之后,可以将当前的行数进行改变,此后的行数都是在此基础上面进行顺延。如图7所示。
图7 改变代码的行数
#line预处理的用法为:
#line number “string”
那么#line预处理在这个C语言代码里面具体有什么作用呢?难道是仅仅为了好玩?其实不然,#line预处理会通知预处理器number是下一行输入的行号。如果给出了可选部分“string”,预处理器就把它作为当前文件的名字。值得注意的是,这条指令将修改__LINE__符号的值。
这条指令常用于把其它语言的代码转换成C语言代码的程序。C语言编译器产生的错误信息可以引用源文件,而不是翻译程序产生的C语言中间源文件的文件名和行号。
5 操作编译器指令#pragma
#pragma指令是另一种机制,用于支持编译器的其它衍生特性。它的语法也是因编译器而异。有些环境可能提供一些#pragma指令,允许一些编译选项或者其它方式无法实现的特殊处理方式,由于这个#pragma关键词不可移植,因此我们在这里也不推荐其使用。
6 无效指令#
无效指令就是一个“#”,这类指令只是被编译器简单地删除,没有其它意义。你可以使用它去彰显一些重要代码的存在。
#
#define MAX 100
#
以上就是关于预处理章节的全部内容。在我们写代码时,熟练使用这些预处理可以使得我们的代码更加紧凑,易读。