C语言预编译、宏的整理

来源:互联网 发布:硬盘坏了数据恢复价格 编辑:程序博客网 时间:2024/05/22 07:49

转载自:https://wenku.baidu.com/view/f20bca7701f69e3143329417.html?from=search ,如有冒犯,请联系删除。
C中的预编译宏定义 注:在VC中,想看到宏展开后的代码,设置在项目->属性->C/C++->预处理器->生成预处理文件 打开这个选项后,去工程下找相应的.i文件,就能看见宏展开后到底是个什么了:) 在将一个C源程序转换为可执行程序的过程中, 编译预处理是最初的步骤. 这一步骤是由预处理器(preprocessor)来完成的. 在源流程序被编译器处理之前, 预处理器首先对源程序中的”宏(macro)”进行处理.
C初学者可能对预处理器没什么概念, 这是情有可原的: 一般的C编译器都将预处理, 汇编, 编译, 连接过程集成到一起了.编译预处理往往在后台运行. 在有的C编译器中, 这些过程统统由一个单独的程序来完成, 编译的不同阶段实现这些不同的功能.可以指定相应的命令选项来执行这些功能. 有的C编译器使用分别的程序来完成这些步骤. 可单独调用这些程序来完成. 在gcc中,进行编译预处理的程序被称为CPP, 它的可执行文件名为cpp.
编译预处理命令的语法与C语言的语法是完全独立的. 比如: 你可以将一个宏扩展为与C语法格格不入的内容, 但该内容与后面的语句结合在一个若能生成合法的C语句, 也是可以正确编译的.
(一) 预处理命令简介
注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于”##”的介绍.
关于定义宏的另外一些问题 (1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的”相同”要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释… e.g.

#define NULL 0#define NULL /* null pointer */ 0 ```上面的重定义是相同的, 但下面的重定义不同:

define fun(x) x+1

define fun(x) x + 1 或: #define fun(y) y+1 “`

如果多次定义时, 再次定义的宏内容是不同的, gcc会给出”NAME redefined”警告信息.
应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.
(2) 在gcc中, 可在命令行中指定对象宏的定义: e.g. $ gcc -Wall -DMAX=100 -o tmp tmp.c 相当于在tmp.c中添加” #define MAX 100”.
那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?
—若-DMAX=1, 则正确编译.
—若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.
注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1
(3) #define所定义的宏的作用域 宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别 e.g.

define ONE 1 sum = ONE + TWO /* sum = 1 + TWO */

define TWO 2 sum = ONE + TWO /* sum = 1 + 2 */

undef ONE sum = ONE + TWO /* sum = ONE + 2 */

char c[] = “TWO” /* c[] = “TWO”, NOT “2”! */
(4) 宏的替换可以是递归的, 所以可以嵌套定义宏. e.g.

define ONE NUMBER_1

define NUMBER_1 1

int a = ONE /* a = 1 */ “`
2, #undef

undef用来取消宏定义, 它与#define对立:

undef name 如够被取消的宏实际上没有被#define所定义, 针对它的#undef并不会产生错误. 当一个宏定义被取消后, 可以再度定义它.

3, #if, #elif, #else, #endif

if, #elif, #else, #endif用于条件编译:

if 常量表达式1 语句…

elif 常量表达式2 语句…

elif 常量表达式3 语句… …

else 语句…

endif

if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制. else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.

使用它们可以提升代码的可移植性—针对不同的平台使用执行不同的语句. 也经常用于大段代码注释. e.g.

if 0 { 一大段代码; }

endif

常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.

if MACRO_NON_DEFINED == #if 0 在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.

注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用”-Wundef”选项会显示宏未定义的警告信息.
4, #ifdef, #ifndef, defined.

ifdef, #ifndef, defined用来测试某个宏是否被定义

ifdef name 或 #ifndef name

它们经常用于避免头文件的重复引用:

ifndef FILE_H

define FILE_H

include “file.h”

endif

defined(name): 若宏被定义,则返回1, 否则返回0. 它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:

if defined(VAX) && defined(UNIX) && !defined(DEBUG)

和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用”-Wundef”选项不会显示宏未定义的警告信息.
5, #include , #include_next

include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.

include “headfile”

include

include 预处理标记 前面两种形式大家都很熟悉, “#include 预处理标记”中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.

实际上, 真正被添加的头文件并不一定就是#include中所指定的文件.

include”headfile”包含的头文件当然是同一个文件,但#include 包包含的”系统头文件”可能是另外的文件. 但这不值得被注意.感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件.

关于#include “headfile”和#include 的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记.
相对于#include, 我们对#include_next不太熟悉.

include_next仅用于特殊的场合.它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊:从当前头文件所在目录之后的目录来搜索头文件. 比如: 头文件的搜索路径一次为A,B,C,D,E.

include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.

可参考cpp手册进一步了解#include_next
6, 预定义宏 标准C中定义了一些对象宏, 这些宏的名称以”__”开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.
下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:
LINE当前语句所在的行号, 以10进制整数标注.
FILE当前源文件的文件名, 以字符串常量标注.
DATE程序被编译的日期, 以”Mmm dd yyyy”格式的字符串标注.
TIME程序被编译的时间, 以”hh:mm:ss”格式的字符串标注, 该时间由asctime返回.
STDC如果当前编译器符合ISO标准, 那么该宏的值为1
STDC_VERSION如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L. 我用gcc, 如果不指定-std=c99, 其他情况都给出STDC_VERSION未定义的错误信息, 咋回事呢?
STDC_HOSTED如果当前系统是”本地系统(hosted)”, 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.
gcc定义的预定义宏:
OPTMIZE如果编译过程中使用了优化, 那么该宏被定义为1.
OPTMIZE_SIZE同上, 但仅在优化是针对代码大小而非速度时才被定义为1.
VERSION显示所用gcc的版本号. 可参考”GCC the complete reference”. 要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null
7, #line

line用来修改LINEFILE. e.g.

printf(“line: %d, file: %s\n”, LINE, FILE);

line 100 “haha” printf(“line: %d, file: %s\n”, LINE, FILE); printf(“line: %d, file: %s\n”, LINE, FILE);

显示: line: 34, file: 1.c line: 100, file: haha line: 101, file: haha
8, #pragma, _Pragma

pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:

pragma GCC name token(s)

pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.

(1) #pragma GCC dependency dependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息. e.g. 在demo.c中给出这样一句:

pragma GCC dependency “temp-file”

然后在demo.c所在的目录新建一个更新的文件: touchtempfile,: gcc demo.c 会给出这样的警告信息: warning: current file is older than temp-file 如果当前文件比指定的文件新, 则不给出任何警告信息.
还可以在在#pragma中给添加自定义的警告信息. e.g.

pragma GCC dependency “temp-file” “demo.c needs to be updated!

1.c:27:38: warning: extra tokens at end of #pragma directive
1.c:27:38: warning: current file is older than temp-file
注意: 后面新增的警告信息要用”“引用起来, 否则gcc将给出警告信息.
(2) #pragma GCC poison token(s) 若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息. e.g.

pragma GCC poison scanf scanf(“%d”, &a); warning: extra tokens at end of #pragma directive error: attempt to use poisoned “scanf”

注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用”“将token(s)引用起来也不行.
(3) #pragma GCC system_header 从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用 #warning显式指明). (这条#pragma语句还没发现用什么大的用处)
由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma: e.g.

#define PRAGMA_DEP #pragma GCC dependency "temp-file" ```由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:

define PRAGMA_DEP _Pragma(“GCC dependency \”temp-file\”“) “`

注意, ()中包含的”“引用之前引该加上\转义字符.
9,
#, ##

和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.

用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.

e.g.

#define TEST(a,b) printf( #a "<" #b "=%d\n", (a)<(b)); ```注意: #只针对紧随其后的token有效!##用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token. e.g.

define TYPE(type, n) type n “`

之后调用: TYPE(int, a) = 1; TYPE(long, b) = 1999; 将被替换为: int a = 1; long b = 1999;
(10) #warning, #error

ifdef DEBUG

define my_printf(…) fprintf(stderr, VA_ARGS)

else

define my_printf(…) printf(VA_ARGS)

endif “`

tokens中的VA_ARGS被替换为函数宏定义中的”…”可变参数列表.
注意在使用#define时候的一些常见错误:

define MAX = 100

define MAX 100;

=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出”;”.

原创粉丝点击