再谈C语言中的预处理系统

这篇具有很好参考价值的文章主要介绍了再谈C语言中的预处理系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C语言中的预处理器系统是一种十分古老而又神奇的存在。其实大部分汇编语言也具备预处理系统。预处理系统其实就是对当前宿主语言的 元语言(Meta-Language),可用来根据当前的环境以及配置来“生成”相应的源代码。因此预处理系统的语法体系与当前宿主语言的语法体系可以被看作为是独立的,我们可以参考GAS汇编语言的预处理语法就更接近于C语言的预处理器。

我们常用的C语言中的预处理器主要有:条件包含(#if#ifdef#else 等),头文件包含(#include),宏替换(#define),行控制(#line),错误指示符(#error),编译杂项(#pragma)以及空指示符(# )。本文将主要介绍前三个。

我们先来谈谈预处理器中的宏。有一定C语言基础的朋友已经知道,在C语言中定义的一个宏,当在源文件中使用它的时候,它会被展开,替换为它原来的形式,比如:#define MY_MACRO 5 + 5,当你用MY_MACRO这宏的时候,像:int a = MY_MACRO * 2;,那么其实这里的语句就相当于:int a = 5 + 5 * 2;,也就是说,MY_MACRO在源文件中不被计算,而是很机械地进行展开。那我们是否就能认为C语言的预处理系统就如此简单,基本就靠替换展开吃饭的呢?大部分情况确实如此,但预处理器系统也有对表达式进行计算的时候!当我们使用了 条件包含 预处理器时,这其中的 整数常量表达式 将会被计算。我们来看以下一个例子:

#define MACRO_VALUE1    5 + 5
#define MACRO_VALUE2    (-6U * 1)

#if MY_DEFINED_MACRO == 0 && 100 % 5 == 0 && MACRO_VALUE1 < MACRO_VALUE2 && (MACRO_VALUE1 * 2 < 20)

#warning MY_DEFINED_MACRO not zero!

#endif // MY_DEFINED_MACRO

以上这个短小的例子所包含的信息量倒是不少。这里首先要强调的一点是,#if 后面必须跟的是 整数常量表达式,如果不满足要求的话,预处理器直接报错。比如以下都是非法的条件包含:

// 这里出现了浮点数字面量,尽管 > 操作之后的结果是个整数常量表达式,
// 但它却是非法的。
#if 5.0 > 1.0

#endif

#define DUMMY_MACRO
// 这里没有对DUMMY_MACRO做任何整数常量表达式的定义,因此也是非法的
#if DUMMY_MACRO

#endif

我们再回到第一个代码例子。这里的#if后面跟着四个条件表达式,并且结果都是“真”。我们先看第一个,MY_DEFINED_MACRO == 0为何是“真”?因为C语言的预处理器有个神奇的约定——如果一个宏符号没有被定义,那么在#if中常量表达式的计算结果即为0,表示“假”,因此这里就相当于“0 == 0”,那么这个比较的结果就是“真”了。
第二个条件表达式比较容易理解,它是一个很常规的整数常量表达式的判断。
第三个又有点儿意思了。我们首先能看到,在对#if进行计算时,如果碰到一个宏符号,那么也会对它做展开。然后,C语言的预处理器是可以计算带符号整数与无符号整数的,由于这里的-6U在32/64位系统环境下其实是 232 - 6,因此结果确实大于 5 + 5,因而条件成立。
在第四个条件表达式中我们可以看到,在做条件包含计算时,宏符号也会被机械式地展开,而不是直接做计算,因此这里的MACRO_VALUE1 * 2展开后也是5 + 5 * 2,结果为15,因此小于20,条件成立。

由此我们可以看到,C语言的条件包含也会做计算,并且也会对之前定义的宏做展开,然后进行计算。


下面我们再来谈谈宏替换。C语言预处理器系统中的宏替换功能确实比较强大,而且大部分符号都能作为有效的宏函数的“实参”。我们可以利用这一点来封装一些固定样式的代码。我们看以下例子:

#define MACRO_NOP_ARG

#define MEMBERS_INITIALIZER(obj_access)     obj_access member0, \
                                            obj_access member1, \
                                            obj_access member2, \
                                            obj_access member3

#define MY_TEST_MACRO(param1, param2)       param1  param2

int main(void)
{
    struct {
        int member0, member1, member2, member3;
    }obj = { 1, 2, 3, 4 };

    int member0 = 10, member1 = 20, member2 = 30, member3 = 40;

    int arrObj[] = { MEMBERS_INITIALIZER(obj.) };
    int arr[] = { MEMBERS_INITIALIZER(MACRO_NOP_ARG) };

    printf("The values are: ");
    for (size_t i = 0; i < sizeof(obj) / sizeof(obj.member0); i++)
        printf("%d  ", arrObj[i] + arr[i]);

    puts("");

    MY_TEST_MACRO(int tst = 100; , (++tst, tst += 10));
    // 输出:tst = 111
    printf("tst = %d\n", tst);
}

上述代码举了两个利用宏替换的例子。第一个例子是通过一个宏函数定义来满足两种不同的数组初始化的效果。
首先,MEMBERS_INITIALIZER宏函数的定义允许使用结构体对象去访问固定的member0到member3这四个成员,表示为:obj_access。不过这个参数可以缺省,若是缺省,那么其实就是直接对变量member0到member3进行依次访问了。不过对于确定参数的宏函数,我们必须要传个实参进去,但这依旧阻止不了我们想将obj_access进行缺省。以上代码例子定义了一个MACRO_NOP_ARG这个宏,这个技巧就在于用这个缺省定义的宏能提供缺省宏函数实参的效果。

第二个例子很夸张地呈现出宏替换的逼格,我们发现,对于宏函数“调用”的实参中充满了=、甚至还有;分号。但这些符号都是合法的,唯独,逗号在此宏函数调用中才起到参数分隔的作用。文章来源地址https://www.toymoban.com/news/detail-424275.html

进阶参考:
  • C语言中的翻译阶段与字符串字面量的拼接
  • 详细漫谈C语言头文件包含 #include <header.h> 与 #include “header.h” 的区别

到了这里,关于再谈C语言中的预处理系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言】预处理

    在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码。 1.翻译环境 组成一个程序的每个源文件通过编译过程分别转换成目标代码 每个目标文件由链接器(linker)捆绑在

    2024年02月17日
    浏览(67)
  • C++中的预处理

    1.__FILE__进行编译的源文件 2.__LINE__文件当前的行号 3.__DATE__文件被编译的日期 4.__TIME文件被编译的时间 5.__STDC__如果编译器遵循ANSIC,其值为1,否则未定义 基本语法:#define 名字 内容   eg.define M 1 经#define定义的常量时不经过任何计算,直接打印  eg #define M 1+1则M = 1 + 1(不是2) 注意

    2024年01月19日
    浏览(49)
  • 详解C语言—预处理

    目录 一、预处理 1、预定义符号介绍 2、预处理指令 #define #define 定义标识符:  #define 定义宏: #define 替换规则 3、预处理操作符# 4、预处理操作符## 5、带副作用的宏参数 6、宏和函数对比 二、命名约定 三、预处理指令 #undef 四、命令行定义 五、条件编译  1、单分支#if:

    2024年02月08日
    浏览(55)
  • 【C语言】预处理详解

             本文目录 1 预定义符号 2 #define 2.1 #define 定义标识符 2.2 #define 定义宏 2.3 #define 替换规则 2.4 #和## 2.5 带副作用的宏参数 2.6 宏和函数对比 2.7 命名约定 3 #undef 4 命令行定义 5 条件编译 6 文件包含 6.1 头文件被包含的方式 6.2 嵌套文件包含 这些预定义符号都是语言内置

    2024年02月14日
    浏览(40)
  • C语言·预处理详解

            C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的                 __FILE__  进行编译的源文件                 __LINE__  文件当前的行号                 __DATE__  文件被编译的日期                 _

    2024年01月21日
    浏览(45)
  • C语言【预处理器】

    1、一些关于预处理的知识 ​C代码中,一般带 # 的都是预处理指令,包括 宏替换、文件包含、条件编译 等。 ​为兼容一些老编译器, # 前后一般不写空格 ​预处理指令后面不加分号。 2、宏定义 3、文件包含 ​自定义头文件,用\\\" \\\" 。 引号里填相对路径或绝对路径。基于当

    2024年02月05日
    浏览(45)
  • C语言:预处理详解

    创作不易,来个三连呗! C语⾔设置了⼀些预定义符号, 可以直接使⽤ ,预定义符号也是在预处理期间处理的。 __FILE__ //进⾏编译的源⽂件 __LINE__ //⽂件当前的⾏号 __DATE__ //⽂件被编译的⽇期 __TIME__ //⽂件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

    2024年01月19日
    浏览(45)
  • C语言预处理详解

    上一篇博客中我们讲了C语言的编译与链接,在编译过程中有三个小阶段:预处理、编译、汇编。 本篇博客将详细讲述预处理部分的有关知识点 。 在C语言中,C语言本身设置了⼀些预定义符号,可以直接使⽤ ,预定义符号的处理也是在预处理期间进行的。 在这里介绍几个常

    2024年04月15日
    浏览(52)
  • C中的预处理,宏

    🐶博主主页: @ᰔᩚ. 一怀明月ꦿ  ❤️‍🔥 专栏系列: 线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++ 🔥 座右铭: “不要等到什么都没有了,才下定决心去做” 🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀 目录 🐰宏

    2024年02月08日
    浏览(30)
  • 【C语言:编译、预处理详解】

    我们都知道,一个程序如果想运行起来要经过编译、链接然后才能生成.exe的文件。 编译⼜可以分解为三个过程: 预处理(有些书也叫预编译)、 编译 汇编 预处理阶段 主要处理那些源文件中以#开始的预编译指令。比如:#include,#define,处理的规则如下: 删除所有的注释

    2024年02月03日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包