• c:预处理指令(#include、#define、#if等)


    环境:

    • window11
    • x86_64-8.1.0-release-win32-seh-rt_v6-rev0.7z (gcc8.1.0)

    环境准备参考:《c/c++: window下安装mingw-w64》

    一、从一个test.c文件的编译说起

    首先,我们要知道一个 test.c 文件是如何一步步编译成 test.exe的。
    总体来说:

    • 第一步:预处理器先处理c文件中的预处理指令,如: #include#if...等,得到一个没有预处理指令的代码文件(txt格式) test.i
    • 第二步:编译器进行词法/语法分析、优化、编译上一步生成的test.i,得到汇编文件(txt格式)test.s
    • 第三步:汇编器将上一步得到的test.s翻译成二进制格式文件test.o
    • 第四步:链接器将上一步得到的test.o和引用的资源做链接生成test.exe

    如下面的示例:

    file:test.c

    #include 
    
    int main()
    {	
    	printf("ok");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    编译如下:
    在这里插入图片描述
    我们注意到:预处理器是在编译器开始之前工作的,预处理器的工作内容包含:

    摘自:《C程序设计(第四版)学习辅导》
    在预处理阶段,预处理器把程序中的注释全部删除;
    对预处理指令进行处理,如:把#include指令指定的头文件(如:stdio.h)的内容复制到#include指令处;
    #define指令,进行指定的字符替换(如:将程序中的符号常量用指定的字符串代替),同时删除预处理指令。

    当预处理器处理完后,生成的test.i将不再包含预处理指令了。

    二、预处理指令有哪些

    在c语言中主要有以下三种预处理指令:

    • 文件包含:#include
    • 宏定义:#define
    • 条件编译:#if...

    下面,我们一一讲解:

    注意:在后面的test.i文件中我们可能看到#pragma,但它不是预处理指令,#pragma是用来指导编译器行为的。。

    三、预处理之文件包含

    #include:简单来讲,它就是将指定的文件拷贝到这个指令的地方,并删除这个#include指令。

    打开,上面我们生成的test.i文件:
    在这里插入图片描述
    这里,因为牵扯到系统库,有很多级联的东西,我们可以改下test.c的代码:
    在这里插入图片描述
    然后进行编译:
    在这里插入图片描述
    然后,我们再来观察生成的 test.i
    在这里插入图片描述

    这下我们能一目了然了吧:

    • #include 做文件内容的拷贝和替换,同时删除所有注释。
    • 头文件可以嵌套引用,替换时做遍历,直到把所有引用的都找到;

    另外,C语言不允许头文件之间循环引用,如果你在c.h中再引用a.h,那么 gcc test.c -E -o test.i将会死循环:
    在这里插入图片描述

    另外,我们注意到:test.c中使用了printf()函数,但是我们并没有#include 也没有报错,这就证明 预处理器只是做了源代码文件的整理和替换,并没有涉及到编译。

    还有几项问题:

    • "stdio.h" 有设么区别?标准库的路径在哪里,我们能不能指定头文件的寻找目录?

      前者表示直接从标准库里找这个头文件,后者表示先在c文件同目录下寻找,找不到再去系统目录下寻找。
      我们从上面第一次编译输出的test.i里能清晰的看到gcc寻找标准库的目录。
      一般我们约定,使用C语言的标准库就是<>这种形式,而其他第三方库或自己写的都用""这种形式。
      .
      如果,我们的头文件和C文件不在一块,可以添加gcc参数,如下:
      在这里插入图片描述
      如果我们用的是vs,可以在工程中配置:
      在这里插入图片描述

    四、预处理之宏定义

    4.1 简单宏定义

    直接看示例:
    在这里插入图片描述
    我们可以看到,预处理器处理完后,代码中不再有 PI这个字符串,它已经被替换成 3.1415926了,就连 #define PI 3.1415926这行也没有了。
    这就是最简单的宏定义了,它的本质和#include一样,还是字符串替换。

    不过,预处理器也并不是无脑的替换,比如,当PI出现在字符串位置时就不会被替换:
    在这里插入图片描述
    另外,宏定义是可以嵌套使用的,如下:
    在这里插入图片描述
    注意:虽然可以嵌套使用,但我们不要死循环了(就像 #include 一样)!!!

    4.2 带参数的宏定义

    宏定义也可以带参数的,就像定义函数一样。。。直接看示例吧:
    在这里插入图片描述
    不过,有几点我们需要注意下:

    • 带参数的宏定义虽然可以实现函数的功能,但它本质还是字符串替换,使用时尤其小心;
    • 带参数的宏定义要求宏名和(之间不能有空格,否则,,,看示例:

      在这里插入图片描述

    • 带参数的宏最好将参数用括号包裹起来,否则,,,看示例:
      在这里插入图片描述

    4.3 取消宏定义

    我们可以使用#undef 取消宏定义,看如下示例:
    在这里插入图片描述

    五、预处理之条件编译

    5.1 语法规则

    一般情况下,C文件中的所有行都会参与编译,但有时希望程序中的一部分只在满足一定条件时才能参与编译。

    对应的语法为:

    #ifdef 标识符
    	程序段1
    #elif 标识符
    	程序段2
    #else
    	程序段3
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    还有

    #if 标识符
    	程序段1
    #elif 标识符
    	程序段2
    #else
    	程序段3
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    直接看示例:
    在这里插入图片描述
    上面是我模拟不同平台的代码,实际上,有个经典的例子:

    在window和linux下调用sleep函数让程序睡眠是不一样的,如果我们代码想同时兼容linux和window的话,我们可以像下面这样写:

    在这里插入图片描述
    我们注意到,#if#ifdef有点像啊,但它们是不同的,#if 后面可以跟常量表达式,而 #ifdef后面只能跟宏名:
    在这里插入图片描述
    在这里插入图片描述
    另外,补充下,还有 #ifndef ,这个表示某个宏不被定义时,如stdio.h中的使用:
    在这里插入图片描述

    5.2 从命令行参数控制条件编译

    我们最常用的场景应该是像 __WIN32__linux__ 一样,代码本身不用定义宏,但可以根据环境传入的值进行选择性编译。

    那么,我们如何从环境传入宏定义呢?我们可以使用-D定义宏,使用-U取消宏定义,如:
    -DMY_MACRO=123-DMY_MACRO-UMY_MACRO

    看下面示例:
    在这里插入图片描述

    六、关于预处理的一些思考

    6.1 “篡改”源代码

    预处理的存在让我们有机会在编译之前去修整我们的代码(本质是字符串替换),但它实际上也算是"篡改"我们的源代码了。

    正因为如此,我们可以在上面test.i中看到会有专门标识各个原文件行号的地方,并且预处理指令删除的地方仍然保留空行,这就是为了在实际编译报错的时候能准确定位到test.c中的位置,而非是test.i中的。

    在这里插入图片描述

    6.1 头文件的作用?

    经过,#include 指令的讲解,我们知道,头文件的内容会被拷贝到源文件中,那么,既然如此,我们手写一个mystdio.h代替stdio.h会不会时一样的效果呢(内容同 stdio.h)?或者说,我们直接在 test.c的上方声明 prinf函数是不是就可以不用 #include 了??

    我们直接来看下面示例:
    在这里插入图片描述
    看了这个例子,是不是对 头文件#include 的认识又多了些。

  • 相关阅读:
    算法leetcode|80. 删除有序数组中的重复项 II(rust重拳出击)
    R语言——taxize(第四部分)
    多个Map进行内容合并
    【操作系统实验】线程的创建+信号量通信
    Linux:简易Shell
    Linux虚拟地址空间
    FineReport智能数据图表- 文本域控件
    软考之系统安全理论基础+例题
    基于数字孪生的车路协同虚拟仿真平台研究
    机器学习实训(4)——支持向量机(补充)
  • 原文地址:https://blog.csdn.net/u010476739/article/details/126800299