千家信息网

C语言之预处理

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,1 #define name value我再学习预处理直接的驱动力是看了php的源码,开头一大推的宏定义器,之前'掌握'的一点#define的用法太少了,根本看不懂源码中宏的处理逻辑和运行的路径。所以
千家信息网最后更新 2026年10月31日C语言之预处理
1 #define name value

  我再学习预处理直接的驱动力是看了php的源码,开头一大推的宏定义器,之前'掌握'的一点#define的用法太少了,根本看不懂源码中宏的处理逻辑和运行的路径。所以再学习预处理器很有必要,里面好多东西其实并不难,只是你没有接触到,等你学习了,就感觉容易了。

  一、宏定义和使用中的坑

  这小节采用先给代码再说明的形式,这样你可以看看每个代码的运行结果是否和你预期的一致!

  宏是什么,宏就是#define机制把指定的参数替换的文本中,这样的实现方式就是宏。使用宏定义可以抽出频繁调用的函数,加快执行的速度。定义如下:#define name(参数) 执行体... "参数"可以是使用逗号分隔的参数列表,这些参数可以被应用到执行体中,必须要注意的是"参数"的左括号必须和宏名字紧邻,不然编辑器会报错,或者被解释成执行体中的一部分。比如你写了一个 TEST(a) a * a 调用执行的时候写上 TEST(1) 实际执行的是替换后的 1 * 1。

  凡事都有利弊,宏定义固然使用方便,并且有着函数不可比拟的执行速度,但是宏定义中存在不少的坑,下面就说一说这个坑。看下面的代码:

 1 #include  2  3 #define TEST(a) a * a 4  5 int main() { 6     int b = TEST(2); 7     int c = TEST(1+2); 8     printf("b=%d, c=%d", b, c); 9     printf("\n\n");10 }

  没有执行的情况下,你感觉得到的结果是多少呢!好多人不加思索的说:b=4,c=9。如果真是这样,就不存在坑了,实际打印出来是:b=4, c=5 ,为什么c的值和预想的会有偏差,其实你把执行体中的值替换一下试试,就不难发现问题了,当输入1+2的时候,宏替换成了 1+2*1+2,当然就是5了。好了明白了,那你学会了吗?学会了再看一个:

 1 #include  2  3 #define TEST(a,b) ((a) > (b) ? (a) : (b)) 4  5 int main() { 6     int zyf = 1; 7     int abc = 2; 8     int ret = TEST(zyf++, abc++); 9     printf("zyf=%d,abc=%d,ret=%d", zyf, abc, ret);10     printf("\n\n");11 }

  输出多少呢,如果是 zyf=2,abc=3,ret=3 就错了,实际结果是:zyf=2,abc=4,ret=3 。道理和前面的一样,只看替换后的结果才能真正看到答案。

  这样的问题防不胜防,怎样才能解决呢,其实办法很简单,错误的原因是执行的顺序和我们预想的不一样,那添加小括号应该可以解决这种问题。 比如 (a) * (a)。这样其实也不是最万全的办法,比如你看这个:ADD(a) (a) + (a) ,如果这样调用:ADD(2) * 5 ,这样又不行了,被替换成了 (a) + (a) * 5 执行顺序和预想的还是不一样,所以还要在最外层加上括号:((a) + (a)),这样就解决了。

  二、预定义符号

  C语言中有几个预定义的符号,还是有必要和大家说上一说,先看一段代码:

 1 #include  2 #include  3 #define VAR_DUMP printf( \ 4     "[\n \tfile:%s\n" \ 5     "\tline:%d\n" \ 6     "\ttime:%s %s\n" \ 7     "\tvalue:%d\n]", \ 8     __FILE__, __LINE__, __DATE__, __TIME__, value \ 9 )10 int main() {11     int value = 1;12     VAR_DUMP;13     printf("\n\n");14 }

  是不是和你在大学学习的有点不一样,最简单的宏定义可以使用#define name value 的方式,当然也可以把值写成一个函数,运行的时候直接替换函数。这个宏定义是封装了调试方法,是打印变量内容能像PHP中var_dump()或者print_r()函数一样,打印出变量的内容。

从这段代码中能学习到几点内容:

1、使用#define可以使任何文本替换到程序中,在主程序中你可以随意使用VAR_DUMP。

2、宏定义不以分号结束,如果非常长的宏定义,你可以在末尾加上反斜杠来分行,保持代码易读性。

3、你可以定义频繁调用的函数为宏定义,这样可以加快执行的速速,具体原因后面会说到。

4、C语言有几个预定的符号需要我们知道,很多时候特别有用:

  __FILE__ 预编译的文件名

__LINE__ 文件当前行的行号(执行到这一行)

  __DATE__ 文件编译的日期

  __TIME__ 文件编译的具体时间

__STDC__ 是否遵循ANSI C (不常用)

最后附上运行结果,如图:

  三、宏替换的过程

  在程序的编译阶段,宏先被执行替换,一般要涉及下面的步骤:

  1、调用宏的地方看是否 进行了 #define定义,如果是就进行替换。

  2、把替换的文本信息插入到替换的位置,其中参数被替换成了实际的值。

  3、#define可以包含其他定义的#define定义的东西,需要注意的是不能出现递归的情况。

  因为替换存在临近字段自动结合,所以可以使用一些巧妙的方案:

 1 #include  2 #include  3  4 #define VAR_DUMP(A,B)\ 5    printf("Value of " #B " is " A "\n", B) 6  7 int main(){ 8     int x = 1; 9     VAR_DUMP("%d", x+2);10 }

  

  四、条件编译和其他宏用法

  在大型的C程序中你能看到许多的条件编译,比如可以根据当前的环境加载不同的宏配置,或者在编译的时候加上直极预设的编译条件。这些东西的实现都离不开条件编译。

  1、条件嵌套,#if #endif 原型:

1 #if condition2     执行体3 #endif

可以根据condition来确定执行体要不要执行,以此来控制在不同的环境下编译成不同的系统。看下面的代码,当把DEBUG定义成非0值时,MAX宏定义是存在的,当定义成0时,程序就会报错。

 1 #include  2  3 #define DEBUG 0 4 #if DEBUG 5     #define MAX(a) ((a) * (a)) 6 #endif 7  8 int main() { 9     int b = MAX(2);10     int c = MAX(1+2);11     printf("b=%d, c=%d", b, c);12     printf("\n\n");13 }

当然#if 也可以与#elif嵌套使用,这样就和我们在函数里使用if else一样了,下面是一段php源码中的一段话,你能看到编译php指定不同的参数,检查不同的环境等等都可以通过预处理中的条件编译开完成。

 1 #ifndef PHP_H 2     #define PHP_H 3  4     #ifdef HAVE_DMALLOC 5         #include  6     #endif 7  8     #define PHP_API_VERSION 20100412 9     #define PHP_HAVE_STREAMS10     #define YYDEBUG 011 12     #include "php_version.h"13     #include "zend.h"14     #include "zend_qsort.h"15     #include "php_compat.h"16     #include "zend_API.h"17 18     #undef sprintf19     #define sprintf php_sprintf20 21     /* PHP's DEBUG value must match Zend's ZEND_DEBUG value */22     #undef PHP_DEBUG23     #define PHP_DEBUG ZEND_DEBUG24 25     #ifdef PHP_WIN3226     #    include "tsrm_win32.h"27     #    include "win95nt.h"28     #    ifdef PHP_EXPORTS29     #        define PHPAPI __declspec(dllexport)30     #    else31     #        define PHPAPI __declspec(dllimport)32     #    endif33     #    define PHP_DIR_SEPARATOR '\\'34     #    define PHP_EOL "\r\n"35     #else36     #    if defined(__GNUC__) && __GNUC__ >= 437     #        define PHPAPI __attribute__ ((visibility("default")))38     #    else39     #        define PHPAPI40     #    endif41 42     #    define THREAD_LS43     #    define PHP_DIR_SEPARATOR '/'44     #    define PHP_EOL "\n"45     #endif46 47     #ifdef NETWARE48         /* For php_get_uname() function */49         #define PHP_UNAME  "NetWare"50         #define PHP_OS      PHP_UNAME51     #endif52 53     #if HAVE_ASSERT_H54         #if PHP_DEBUG55             #undef NDEBUG56         #else57             #ifndef NDEBUG58                 #define NDEBUG59             #endif60         #endif61         #include 62 63     #else /* HAVE_ASSERT_H */64         #define assert(expr) ((void) (0))65     #endif /* HAVE_ASSERT_H */66 67     #define APACHE 068     #if HAVE_UNIX_H69         #include 70     #endif71 72     #if HAVE_ALLOCA_H73         #include 74     #endif75 76     #if HAVE_BUILD_DEFS_H77         #include 78     #endif79     . . .

  2、是否已经被定义

被定义:#if define() 或者是#ifdef

  不被定义:#if !define() 或者是#ifndef

  前者的写法虽然没有后者精炼,但是前者有更多的使用场景,比如下面这种,可以进行嵌套执行。

1 #if defined(DEBUG)2      #ifdef DEBUGTWO3            #define TEST(a) a * a4      #endif5 #endif

  3、移除一个宏定义,当不再使用一个宏定义后,可以使用undef来把不需要的宏移除,原型:

1 #undef name

 

  五、宏命名规则和与函数区别

  从前面的使用中我们可以看到,宏的使用规则和函数真是一模一样,但是本质上还是有区别的,在使用中怎样区别宏和函数,涉及到代码规范和代码的可读性问题。标准的宏使用应该使用大写字母,这样在程序中任意地方使用宏都会知道这是一个宏定义。比如前面用到的 #define TEST(a) ((a) * (a))。

  宏与函数区别有以下几点:

  1、执行速度上,宏定义更快,函数因为需要调用栈,存在调用,返回,保存现场的系统开销,所以比宏要慢。

  2、代码长度上,宏在代码长度上实际是增长的,每一处的使用宏都会把name替换成宏内容如果大量使用,会是代码显著增长,新航道雅思培训函数代码只有一份,比较节省代码空间。

  3、参数类型上,宏没有参数类型,只要可以 使用都行。函数不一样,函数有参数类型确定性。正式因为这样,有些宏能巧妙的利用这一点,完成函数不能完成的任务,看下面代码(书上看的),巧妙的利用传递类型无限制的特点自动开辟想要的各种类型空间:

1 #include 2 #include 3 4 #define CREATE_P(nums, type) ((type *) malloc((nums) * sizeof(type)))5 6 int main(){7     int nums = 2;8     CREATE_P(nums, int);9 }

  4、宏定义和函数的使用场景,宏定义一般在程序的开头,函数转化成宏定义一定要考虑成本问题,短小精炼的函数转化成宏使用时最好的,功能负责的函数转化成宏就有点得不偿失了。

  六、文件包含

  1、本地文件包含和库文件包含  

  文件包含在大型系统中必然会用到,大型系统宏定义巨多无比,不可能把所有的宏定义都复制到每个文件中,那么文件包含就能解决这种问题。

  实际上编辑器支持两种文件包含,一种是我们经常会用的库文件的包含,比如上面我们看到的:#include ,还有一种是本地文件包含,说白了就是我们自己写的文件,包含的原型如下:

1 #include 2 #include "filename"

  这两种方式都可以进行文件的包含,不同的是第一种是库文件的包含,标准的C库函数都会以.h扩展名结尾,第二种是本地文件包含,当编辑器看到第二种方式时,优先查找本路径下得本地库文件,如果没有找到就会像包含库文件那样在指定的路径下去找,这时第二种和第一种就差不多了。第二种包含方式在编码习惯上也是比较好的,别人看你的代码很容易知道这个文件是库函数还是你自己写的。

  1、嵌套文件包含

  大型系统中不仅有大量的文件包含,还会有大量的嵌套文件包含,看下面的例子:

  a.h,b.h,c.h,define.c文件,其中a,b,c,define文件的内容如下:

 1 a.h: 2 #include "c.h" 3 void var_dumpa(){ 4     test obja; 5     obja.a[1] = 2; 6     printf("obja.a[1]: %d\n", obja.a[1]); 7 } 8  9 b.h:10 #include "c.h"11 void var_dumpb(){12     test objb;13     objb.a[1] = 2;14     printf("objb.a[1]: %d\n", objb.a[1]);15 }16 17 c.h:18 #include 19 #include 20 21 typedef struct test{22     int a[10];23 }test;24 25 define.c:26 #include 27 #include "a.h"28 #include "b.h"29 30 int main() {31     var_dumpa();32     var_dumpb();33     printf("\n\n");34 }

  ab文件包含c文件,define.c文件文件引用a,b文件后会引发一个错误:typedef struct test类型错误,因为c.h文件被包含了两次,像这种情况在大型系统中会经常遇到,或者说,你会发现重复引用库文件也不会报错,由此可见,库文件一定是使用了解决办法。其实解决这种错误的方案就是采用条件编译,当这个文件引入到另一个文件中后我们可以设置一个宏定义,比如:

1 #include 2 #include 3 4 #ifndef PATH_C_H5     #define PATH_C_H 16     typedef struct test{7         int a[10];8     }test;9 #endif

因为每次编译编译器都会读入整个头文件,如果把所有的文件都加上这个条件编译的话,那交叉引用文件产生的重复宏编译问题就解决了,运行如下:

  好了,就写这么多吧,重新梳理了对宏定义的认识和基本的使用。时间仓促,出错的地方请大婶们一定指出,万分感谢!


文件 函数 代码 编译 参数 条件 库文件 问题 不同 实际 程序 类型 系统 内容 就是 方式 时候 结果 面的 c.h 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 百单网互联网科技有限公司 广州为想互联网科技有限公司 华服互联网科技怎么样 互联网巨头反垄断金融科技 互联网科技媒体 亿账通互联网科技 互联网科技视频素材 互联网科技公司做什么 崇明区互联网教育科技售后服务 未来五年的互联网科技 四川慕妮娅互联网科技有限公司 智帮互联网科技公司最近动向 江苏苏星互联网科技有限公司 银行科技岗属于互联网吗 厦门江牛科技互联网 互联网风格科技公司名称 广州互联网高科技取餐柜 九思连强互联网电子科技有限公司 互联网对科技影响 利用互联网等科技手段 互联网科技 河南云网互联网络科技有限公司 易互联网科技有限公司 丰台科技园 互联网 隆华科技工业互联网 创业天下互联网科技有限公司 和信科技工业互联网 上海钢联属于互联网科技股吗 中国互联网公司的科技投入 互联网科技企业文化
0