C中的预编译宏定义
- 格式:doc
- 大小:46.00 KB
- 文档页数:10
c编译器预定义宏(转载)为了⽅便处理⼀些有⽤的信息,预处理器定义了⼀些预处理标识符,也就是预定义宏。
预定义宏的名称都是以“__”(两条下划线)开头和结尾的,如果宏名是由两个单词组成,那么中间以“_”(⼀条下划线)进⾏连接。
并且,宏名称⼀般都由⼤写字符组成。
在⽇常项⽬编程中,预定义宏尤其对多⽬标平台代码的编写通常具有重⼤意义。
通过预定义宏,程序员使⽤“#ifdef”与“#endif”等预处理指令,就可使平台相关代码只在适合于当前平台的代码上编译,从⽽在同⼀套代码中完成对多平台的⽀持。
从这个意义上讲,平台信息相关的宏越丰富,代码的多平台⽀持越准确。
标准 C 语⾔提供的⼀些标准预定义宏如表 1 所⽰。
表 1 常⽤的标准预定义宏宏描述__DATE__丐前源⽂件的编泽⼝期,⽤ “Mmm dd yyy”形式的字符串常量表⽰__FILE__当前源⽂件的名称,⽤字符串常量表⽰__LINE__当前源义件中的⾏号,⽤⼗进制整数常量表⽰,它可以随#line指令改变__TIME__当前源⽂件的最新编译吋间,⽤“hh:mm:ss”形式的宁符串常量表⽰__STDC__如果今前编泽器符合ISO标准,那么该宏的值为1,否则未定义__STDC_VERSION__如果当前编译器符合C89,那么它被定义为199409L;如果符合C99,那么它被定义为199901L:在其他情况下,该宏为宋定义__STDC_HOSTED__(C99)如果当前是宿主系统,则该宏的值为1;如果当前是独⽴系统,则该宏的值为0__STDC_IEC_559_(C99)如果浮点数的实现符合IEC 60559标准时,则该宏的值为1,否则为未定义__STDC_IEC_559_COMPLEX__(C99)如果复数运算实现符合IEC60559标准时,则该宏的伉为1,否则为未定义__STDC_ISO_10646__(C99 )定义为长整型常量,yyyymmL表⽰wchai_t值遵循ISO 10646标准及其指定年⽉的修订补充,否则该宏为未定义除标准 C 语⾔提供的标准宏之外,各种编译器也都提供了⾃⼰的⾃定义预定义宏。
c语⾔的预处理指令分3种 1宏定义 2条件编译 3⽂件包含宏简介1.C语⾔在对源程序进⾏编译之前,会先对⼀些特殊的预处理指令作解释(⽐如之前使⽤的#include⽂件包含指令),产⽣⼀个新的源程序(这个过程称为编译预处理),之后再进⾏通常的编译所有的预处理指令都是以#开头,并且结尾不⽤分号2.预处理指令分3种 1> 宏定义 2> 条件编译 3> ⽂件包含3.预处理指令在代码翻译成0和1之前执⾏4.预处理的位置是随便写的5.预处理指令的作⽤域:从编写指令的那⼀⾏开始,⼀直到⽂件结尾,可以⽤#undef取消宏定义的作⽤6.宏名⼀般⽤⼤写或者以k开头,变量名⼀般⽤⼩写 宏定义可以分为2种:不带参数的宏定义和带参数的宏定义。
⼀、不带参数的宏定义1.⼀般形式#define 宏名字符串⽐如#define ABC 10右边的字符串也可以省略,⽐如#define ABC2.作⽤它的作⽤是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常⽤来定义常量.3.使⽤习惯与注意1> 宏名⼀般⽤⼤写字母,以便与变量名区别开来,但⽤⼩写也没有语法错误2> 对程序中⽤双引号扩起来的字符串内的字符,不进⾏宏的替换操作。
3> 在编译预处理⽤字符串替换宏名时,不作语法检查,只是简单的字符串替换。
只有在编译的时候才对已经展开宏名的源程序进⾏语法检查4> 宏名的有效范围是从定义位置到⽂件结束。
如果需要终⽌宏定义的作⽤域,可以⽤#undef命令5> 定义⼀个宏时可以引⽤已经定义的宏名#define R 3.0#define PI 3.14#define L 2*PI*R#define S PI*R*R举例1 #include <stdio.h>2#define COUNT 434int main()5 {6char *name = "COUNT";78 printf("%s\n", name);910int ages[COUNT] = {1, 2, 67, 89};1112#define kCount 41314for ( int i = 0; i<COUNT; i++) {15 printf("%d\n", ages[i]);16 }1718// 从这⾏开始,COUNT这个宏就失效19#undef COUNT2021//int a = COUNT 写这个报错2223return0;24 }⼆、带参数的宏定义1.⼀般形式#define 宏名(参数列表) 字符串2.作⽤在编译预处理时,将源程序中所有宏名替换成字符串,并且将字符串中的参数⽤宏名右边参数列表中的参数替换3.使⽤注意1> 宏名和参数列表之间不能有空格,否则空格后⾯的所有字符串都作为替换的字符串2> 带参数的宏在展开时,只作简单的字符和参数的替换,不进⾏任何计算操作。
gcc中预定义的宏__GNUC__今天在看Linux系统编程这本书的代码的时候看到了__GNUC__,不太清楚这个宏所以去查了⼀下,以此记录。
GNU C预定义了⼀系列的宏,这些宏都是以双下划线开始的,这⾥只讲⼀下__GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__,其他GNU C的预定义宏可以到这⾥查看:__GNUC__ 、__GNUC_MINOR__ 、__GNUC_PATCHLEVEL__分别代表gcc的主版本号,次版本号,修正版本号。
这⾥引⽤⼀下上⾯的官⽅说明:__GNUC____GNUC_MINOR____GNUC_PATCHLEVEL__These macros are defined by all GNU compilers that use the C preprocessor: C, C++, Objective-C and Fortran. Their values are the major version, minor version, and patch level of the compiler, as integer constants. For example, GCC 3.2.1 will define __GNUC__ to3, __GNUC_MINOR__ to 2, and __GNUC_PATCHLEVEL__ to 1. These macros are also defined if you invoke the preprocessor directly.__GNUC_PATCHLEVEL__ is new to GCC 3.0; it is also present in the widely-used development snapshots leading up to 3.0 (which identify themselves as GCC 2.96 or 2.97, depending on which snapshot you have).If all you need to know is whether or not your program is being compiled by GCC, or a non-GCC compiler that claims to accept the GNU C dialects, you can simply test __GNUC__. If you need to write code which depends on a specific version, you must be more careful. Each time the minor version is increased, the patch level is reset to zero; each time the major version is increased (which happens rarely), the minor version and patch level are reset. If you wish to use the predefined macros directly in the conditional, you will need to write it like this:/* Test for GCC > 3.2.0 */#if __GNUC__ > 3 || \(__GNUC__ == 3 && (__GNUC_MINOR__ > 2 || \(__GNUC_MINOR__ == 2 && \__GNUC_PATCHLEVEL__ > 0))Another approach is to use the predefined macros to calculate a single number, then compare that against a threshold:#define GCC_VERSION (__GNUC__ * 10000 \+ __GNUC_MINOR__ * 100 \+ __GNUC_PATCHLEVEL__).../* Test for GCC > 3.2.0 */#if GCC_VERSION > 30200Many people find this form easier to understand.上⾯这⼀⼤段英语实际是在讲:注意,__GNUC_PATCHLEVEL__是从gcc 3.0以后才有的,在这之前的gcc是没有预定义这个宏的。
c语言中宏定义在C语言中,宏定义是通过预处理器指令#define来实现的。
宏定义允许我们为代码中的常量、表达式或代码块定义一个名称,这样在代码中就可以使用这个名称来代替实际的常量、表达式或代码块。
宏定义的基本语法如下:c复制代码#define宏名称替换文本这里,宏名称是你定义的宏的名称,而替换文本是当宏在代码中被使用时,它将被替换成的文本。
1、定义常量:c复制代码#define PI 3.14159在代码中,每次你使用PI,它都会被预处理器替换为3.14159。
c复制代码double circle_area = PI * radius * radius;2、定义简单的表达式:c复制代码#define SQUARE(x) (x * x)使用这个宏,你可以轻松地计算任何数的平方。
c复制代码int y = SQUARE(5); // y 的值为 253、定义复杂的代码块:c复制代码#define MAX(a, b) ((a) > (b) ? (a) : (b))这个宏接受两个参数并返回它们之间的最大值。
c复制代码int max_value = MAX(3, 7); // max_value 的值为 7注意事项●宏定义只是简单的文本替换,没有类型检查或作用域限制。
●在定义带参数的宏时,最好将参数用括号包围起来,以避免由于运算符优先级导致的意外结果。
●宏可能会导致代码膨胀,因为预处理器会将每个宏调用替换为其对应的文本。
●宏不是函数,它们不会在调用时产生函数调用的开销。
但是,如果宏很复杂,它们可能会导致代码难以理解和维护。
总的来说,宏定义是C语言中一种强大的工具,但也需要谨慎使用,以避免潜在的问题。
c语言程序中的预编译命令预编译命令是在编译之前就交由编译器进行相关处理的指令,通常以"#"开头。
预编译命令在整个程序开始编译之前就已经执行。
以下是一些常用的预编译命令:1. #include:用于引入头文件。
例如:```C#include <stdio.h> //引入标准输入输出头文件#include "myheader.h" //引入自定义头文件myheader.h```2. #define:用于定义宏。
例如:```C#define PI 3.14159 //定义宏PI,宏的值是3.14159```3. #undef:用于取消之前定义的宏。
例如:```C#undef PI //取消之前定义的宏PI```4. #ifdef,#ifndef,#endif:用于条件编译。
例如:```C#ifdef PI// 如果宏PI已被定义,则编译以下代码#endif#ifndef PI// 如果宏PI未被定义,则编译以下代码#endif```5. #if, #else, #elif:也是用于条件编译。
例如:```C#if defined(PI)//如果宏PI已被定义,则编译以下代码#else//如果宏PI未被定义,则编译以下代码#endif```6. #error:当某些条件不满足时,输出错误并停止编译。
例如:```C#error "Something went wrong"```7. #pragma:用于实现一些特殊的功能,这个指令和编译器实现有关。
例如:```C#pragma pack(1) //设置结构体的对齐方式为1字节对齐```。
c语言define函数C语言中的define函数又称为宏定义,是一种预编译指令,用于定义一个字符串常量或一个带参数的代码片段。
定义的宏可以在代码中使用,实际上是将宏定义的内容在预处理阶段替换为实际的值或代码。
#define 宏名称宏值宏值可以是一个常量、一个表达式、一段代码块或是一个函数调用,其中还可以带有参数。
当程序执行到define函数所在的语句时,预处理器将自动替换宏名称为宏值,然后将替换后的代码作为源程序继续编译。
下面是一个简单的宏定义示例:#define PI 3.14159上面的宏定义定义了一个常量PI,它的值为3.14159。
在程序中使用时,可以直接使用宏名称PI代替它的值来进行计算或赋值操作。
宏定义还可以带有参数,这种宏定义称为带参数的宏定义。
带参数的宏定义可以将一段代码片段封装起来,然后给予不同的参数来形成不同的代码。
这种宏定义带参数的语法如下:#define SQUARE(x) ((x) * (x))上面的宏定义定义了一个带一个参数x的宏SQUARE(x),它的宏值是一个计算平方值的表达式。
在程序中使用时,可以使用SQUARE(x)来代替(x) * (x),从而计算一个数字的平方值。
宏定义还可以使用#if n defined语句来探测宏是否已经定义过。
这种语句可以在宏定义之前使用,以保证不会重复定义宏。
下面是一个使用#if n defined语句的宏定义示例:上面的宏定义语句中,#ifndef PI表示如果PI未被定义,则执行宏定义语句,并定义PI为3.14159。
如果PI已经被定义,则不执行宏定义语句。
需要注意的是,宏定义只是在编译前进行替换操作,它并不是一个函数或是一个变量。
因此,宏定义不能被修改或赋值,也不能在程序中直接传递给函数。
另外,宏定义也有一定的局限性,不能包含比较复杂的逻辑运算或函数调用。
若要进行复杂的计算或使用函数调用,则需要编写实际的函数来进行处理。
总的来说,define函数是一种非常方便的宏定义技术,可以用于简化代码的书写和加速程序的执行。
C语⾔预处理命令--宏定义⼀、宏讲解1、宏定义宏(Macro),是⼀种的称谓。
⾥的宏是⼀种(Abstraction),它根据⼀系列预定义的规则替换⼀定的⽂本模式。
或在遇到宏时会⾃动进⾏这⼀模式替换。
2、C语⾔宏定义的常规⽤法1) 定义符号常量#define PI 3.1415926#define MAX_N 100002) 定义傻⽠表达式(注意,定义的这种表达式⼀不⼩⼼很容易出现bug,下⽂会讲)#define S(a, b) a * b#define MAX(a, b) (a) > (b) ? (a) : (b)3) 定义代码段#define P(a) {\ printf("%d\n", a);\} ps:编译器对于宏的解析是很严谨的,只能⽀持⼀⾏解析,\是起连接作⽤,表⽰当⾏的宏代码与下⼀⾏宏连接在⼀起,使得编译器当成⼀⾏看待。
3、编译器预定义的宏在C语⾔中,我们有很多预定义的宏,就是C语⾔帮程序员预先定义好的宏,可以让我们使⽤。
宏说明__DATE__ ⽇期:Mmm dd yyyy__TIME__ 时间:hh:mm:ss__LINE__ 当前源⽂件的代码⾏号__FILE__ ⽂件名__func__ 函数名/⾮标准__FUNC__ 函数名/⾮标准__PRETTY_FUNCTION__ 更详细的函数信息/⾮标准4、预定义命令-条件式编译函数说明#ifdef DEBUG 是否定义了DEBUG宏#ifndef DEBUG 是否没有定义DEBUG宏#if MAX_N == 5 宏MAX_N是否等于5#elif MAX_N == 4 否则宏MAX_N是否等于4#else#endif5、预定义命令从上图可以看到: 预编译 将.c ⽂件转化成 .i⽂件 使⽤的gcc命令是:gcc –E 对应于预处理命令cpp 编译 将.c/.h⽂件转换成.s⽂件 使⽤的gcc命令是:gcc –S 对应于编译命令 cc –S 汇编 将.s ⽂件转化成 .o⽂件 使⽤的gcc 命令是:gcc –c 对应于汇编命令是 as 链接 将.o⽂件转化成可执⾏程序 使⽤的gcc 命令是: gcc 对应于链接命令是 ld 总结起来编译过程就上⾯的四个过程:预编译、编译、汇编、链接。
visualc++中预定义的宏⼀、主要⽬标(由于visual studio通常包含很多开发环境,通常将其中c/c++的ide称为visual c++ 20xx)整理下visual c++ 2010下预定义的宏。
做⼀下备忘和了解,在实际编码中可以考虑使⽤部分宏作为判断标准。
⼆、vc++ 2010中预定义宏注意本⽂中提及的所有预定义宏都是⽆参数的,并且不能重定义。
1. __FILE__、__LINE__这两个预定义宏是c语⾔中定义的,主要⽤于标识代码段所在的源⽂件名称(全路径或者⽂件名)以及当前位置在源⽂件中的⾏数。
多⽤于错误跟踪、预编译报错、程序异常跟踪等,⽐如TRACE、ASSERT失败的提⽰信息。
2. _CHAR_UNSIGNEDvc中附加定义的宏。
主要⽤于说明char类型的数据,是有符号的还是⽆符号的。
默认情况下char是⽆符号的。
在很多开源项⽬⾥⾯,使⽤cl或gcc编译源码时可能需要关注下char的正负:因为有符号的char在做数值运⾏遇到类型提升的情况可能会存在不确定性的⾏为(跟编译器的实现有关)。
3. __FUNCDNAME__、__FUNCSIG__、__FUNCTION__vc中定义的宏。
仅在函数实现中使⽤有效。
三个宏分别表⽰:编译器修饰后的函数名称、函数签名、代码中的函数名称。
实际使⽤可参考下列代码:void exampleFunction(){printf("Function name: %s\n", __FUNCTION__);printf("Decorated function name: %s\n", __FUNCDNAME__);printf("Function signature: %s\n", __FUNCSIG__);// vc++ 2010的实际输出// -------------------------------------------------// Function name: exampleFunction// Decorated function name: ?exampleFunction@@YAXXZ// Function signature: void __cdecl exampleFunction(void)}View Code4. _WIN32、_WIN64vc中定义的宏。
在将一个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 1sum = ONE + TWO /* sum = 1 + TWO */#define TWO 2sum = ONE + TWO /* sum = 1 + 2 */#undef ONEsum = ONE + TWO /* sum = ONE + 2 */char c[] = "TWO" /* c[] = "TWO", NOT "2"! */(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.e.g.# define ONE NUMBER_1# define NUMBER_1 1int 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"#endifdefined(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"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_next6, 预定义宏标准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/null7, #line#line用来修改__LINE__和__FILE__.e.g.printf("line: %d, file: %s ", __LINE__, __FILE__);#line 100 "haha"printf("line: %d, file: %s ", __LINE__, __FILE__);printf("line: %d, file: %s ", __LINE__, __FILE__);显示:line: 34, file: 1.cline: 100, file: hahaline: 101, file: haha8, #pragma, _Pragma#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:#pragma GCC name token(s)#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.(1) #pragma GCC dependencydependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息.e.g.在demo.c中给出这样一句:#pragma GCC dependency "temp-file"然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ 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 directive1.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 scanfscanf("%d", &a);warning: extra tokens at end of #pragma directiveerror: 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 ", (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#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:#warning tokens#error tokense.g.#warning "some warning"注意, #error和#warning后的token要用""引用起来!(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.--------------------------------------------------------------------------------预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下:#define 定义一个预处理宏#undef 取消宏的定义#include 包含文件命令#include_next 与#include相似, 但它有着特殊的用途#if 编译预处理中的条件命令, 相当于C语法中的if语句#ifdef 判断某个宏是否被定义, 若已定义, 执行随后的语句#ifndef 与#ifdef相反, 判断某个宏是否未被定义#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else#endif #if, #ifdef, #ifndef这些条件命令的结束标志.defined 与#if, #elif配合使用, 判断某个宏是否被定义#line 标志该语句所在的行号# 将宏参数替代为以参数值为内容的字符窜常量## 将两个相邻的标记(token)连接为一个单独的标记#pragma 说明编译器信息#warning 显示编译警告信息#error 显示编译错误信息(二) 预处理的文法--------------------------------------------------------------------------------预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志.预处理语句格式: #command name(...) token(s)1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容.2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99).3, 语句中可以利用"\"来换行.e.g.# define ONE 1 /* ONE == 1 */等价于: #define ONE 1#define err(flag, msg) if(flag) \printf(msg)等价于: #define err(flag, msg) if(flag) printf(msg)(三) 预处理命令详述--------------------------------------------------------------------------------1, #define#define命令定义一个宏:#define MACRO_NAME(args) tokens(opt)之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的.对象宏不带参数的宏被称为"对象宏(objectlike macro)"#define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量.e.g.#define MAX 100int a[MAX];#ifndef __FILE_H__#define __FILE_H__#include "file.h"#endif#define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件.要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容.函数宏带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率.函数宏的参数是固定的情况函数宏的定义采用这样的方式: #define name( args ) tokens其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号.注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格.e.g.#define mul(x,y) ((x)*(y))注意, 函数宏之后的参数要用括号括起来, 看看这个例子:e.g.#define mul(x,y) x*y"mul(1, 2+2);" 将被扩展为: 1*2 + 2同样, 整个标记串也应该用括号引用起来:e.g.#define mul(x,y) (x)*(y)sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句:e.g.mul (f(a,b), g(c,d));e.g.#define insert(stmt) stmtinsert ( a=1; b=2;) 相当于在代码中加入 a=1; b=2 .insert ( a=1, b=2;) 就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符.insert ((a=1, b=2;)) 可解决上述问题.在定义和调用函数宏时候, 要注意一些问题:1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题. example_3.7:#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp}; 明显后面的;是多余的, 我们应该这样调用: swap(1,2)虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容:#define swap(x,y) \do { unsigned long _temp=x; x=y; y=_tmp} while (0)swap(1,2); 将被替换为:do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明.eg_3.8:#define incr(v, low, high) \for ((v) = (low),; (v) <= (high); (v)++)只能以这样的形式被调用: incr(a, 1, 10) /* increase a form 1 to 10 */函数宏中的参数包括可变参数列表的情况C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表.#define name(args, ...) tokens#define name(...) tokens"..."代表可变参数列表, 如果它不是仅有的参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃).通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况.e.g.#ifdef DEBUG#define my_printf(...) fprintf(stderr, __VA_ARGS__)#else#define my_printf(...) printf(__VA_ARGS__)#endiftokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表.注意在使用#define时候的一些常见错误:#define MAX = 100#define MAX 100;=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";".。