完整教程:C 语言宏函数进阶:逗号表达式与 GNU 拓展的妙用

news/2025/9/25 21:00:21/文章来源:https://www.cnblogs.com/tlnshuju/p/19112015

完整教程:C 语言宏函数进阶:逗号表达式与 GNU 拓展的妙用

2025-09-25 20:58  tlnshuju  阅读(0)  评论(0)    收藏  举报

   在 C 语言中,宏(Macro)是一种强大的预处理工具,而 “宏函数”(通过宏模拟函数功能)更是以其零调用开销的特性被广泛使用。但宏的语法灵活且容易踩坑,尤其是在处理多语句、变量声明时,逗号表达式和 GNU 拓展能帮我们解决不少问题。本文结合实际场景,聊聊宏函数的使用技巧与进阶玩法。

一、宏函数的基础:不是函数,胜似函数

宏函数本质是 “文本替换”,用#define定义,在预处理阶段直接展开到代码中,避免了函数调用的栈帧开销。比如实现一个简单的加法:

#define ADD(a, b) ((a) + (b))
//  int sum = ADD(3,4);
// 在预处理阶段进行宏替换  int sum = ((3)+(4))
// 本质上还是把左侧的符号替换成右侧的表达式

宏函数的优势与坑点

  • 优势:无函数调用开销,适合简单逻辑(如数值计算、参数检查)。
  • 坑点
    1. 缺乏类型检查:ADD(1, "abc")在编译阶段语法分析报错时报错。
    2. 优先级问题:若不加括号,ADD(1, 2)*3会展开为1+2*3(正确应为(1+2)*3)。
    3. 副作用风险:若参数含自增 / 自减(如ADD(i++, j++)),会导致多次执行(展开后为(i++) + (j++),符合预期,但复杂场景可能出错)。

二、逗号表达式:宏中执行多操作的利器

   逗号表达式(expr1, expr2, ..., exprN)的特性是:从左到右依次执行,返回最后一个表达式的值。这在宏中可实现 “多步操作并返回结果”。

示例:自增后返回新值

// 先将x自增1,再返回自增后的值
#define INC_RET(x) ((x)++, (x))
int a = 3;
printf("%d\n", INC_RET(a)); // 输出4,等价于a++后返回a

逗号表达式的限制

    逗号号表达式主要有 值,

  • 变量:a(结果是变量a的值)
  • 运算:x + 3(结果是x加 3 的和)
  • 赋值:p = &b(结果是指针p的新值)
  • 函数调用:getchar()(结果是输入的字符)

 看吧 #define ADD(a,x,b,p)   (a,x,(x++),p=&b,printf("i m a func from macro"),(x+3+4+5))
  看看上面这个宏函数即可认识到这一点

  可是有一些场景 比如我想实现一个东东: 后置加n的功能的功能: 那我们需要一个中间变量,

   #define ADD_SUFFIX(x,n) (int temp = x,x+=n,temp) 我们的愿意是让temp返回原值就好了

但是这个声明 int temp =x 它并没有返回值,并不算作逗号表达式啊! 所以这不行如何解决? 看课 GNU的拓展

三、GNU 拓展:突破标准 C 的限制

   标准 C 的语法对宏的灵活性有诸多限制,而 GNU C 提供了一些实用拓展,其中最常用的是 **“语句表达式”(Compound Statements as Expressions)**。

语句表达式:({ ... })  花括号表示代码块

       介绍一些代码块吧:

       如果你用过if语句,那你肯定知道如果不加上 { } 花括号,if只能控制紧跟它的一条语句。

 加上而被{}圈起来的区域,它可以控制紧跟它的一个{}区域。 那是因为编译器会把这一区域的代码看作一个整体称之为代码块。

      用({ ... })包裹的代码块,在 GNU C 中被视为一个 “表达式”,其值为块中最后一个表达式的值。更重要的是:内部可以声明变量

解决 va_arg 的痛点

   之前你想实现 “先保存当前指针,移动指针后返回原值” 的逻辑,用 GNU 拓展可以这样写:

// GNU拓展写法:清晰且安全
#define va_arg(ap, type) ({ \
type *temp = (type*)ap;  // 声明临时变量,保存当前地址 \
ap += sizeof(type);      // 移动指针到下一个参数 \
*temp;                   // 返回当前参数值(最后一个表达式为结果) \
})
// 这里为什么也要加上()原因 本质上是一个优先级以及深刻理解
// 如果说 我们的if-else 语句中 有两条花括号会报错 这样揭示了本质上还是宏替换嘛!

  这段代码中,{ ... }内部声明了temp变量,避开了逗号表达式不能声明变量的限制,逻辑更直观。

标准 C 的替代方案

   如果需要兼容标准 C(不依赖 GNU 拓展),可以用纯逗号表达式实现(逻辑稍绕但符合标准):

// 标准C写法:用指针运算替代临时变量
#define va_arg(ap, type)  (*(type*)((ap) += sizeof(type), (ap) - sizeof(type)))

   原理:先通过ap += sizeof(type)移动指针,再用ap - sizeof(type)获取原地址,最后解引用返回值。 这里很巧妙的利用了逗号表达式!

四、宏函数的高级技巧:多语句安全包裹

当宏包含多条语句时,直接写会导致if等结构出错。例如:

// 错误示例:多语句宏未包裹
#define INIT(x, y) x = 0; y = 0;
if (flag) INIT(a, b); // 展开后:if(flag) a=0; y=0; 导致y=0不受if控制
else ...; // 报错:else没有对应的if

解决:用do-while(0)包裹

  标准 C 中,do-while(0)可将多语句转为 “单条语句”,避免语法错误:

// 正确示例:多语句宏用do-while(0)包裹
#define INIT(x, y) do { x = 0; y = 0; } while(0)
if (flag) INIT(a, b); // 展开后:if(flag) do { ... } while(0); 语法正确
else ...; // 正常执行

      简单解释一些这里用的do while(0);语句 首先宏函数,本质上依然是宏替换!,现在做了一个事情就是想实现多语句,if后面会控制一个代码块用{}括起来的 ,

  举例这个场景 如果入门修改  #define INIT(x, y)  { x = 0; y = 0; }  宏替换之后就变为

   if(flag) {x=0;y=0} ; 看见了吗这个你自己加的分号就会影响我们的if-else语句 else会找不到你的if语句 而 do -while(0)语句本身就是最后要加上; 所以宏替换后没有影响。 

 五、总结:宏函数的正确打开方式

  1. 基础原则:宏中变量加括号,避免优先级问题;慎用带副作用的参数(如i++)这里要深刻理解宏替换的本质。
  2. 逗号表达式:适合简单多操作场景(无变量声明),利用其 “返回最后一个值” 的特性,深刻理解自减和-会不会影响值的变化以及逗号表达式就可以轻松驾驭宏函数。
  3. GNU 拓展({ ... })适合需要声明变量的复杂逻辑,代码更清晰,但牺牲了部分可移植性,毕竟有的系统不支持GNU拓展啊!。
  4. 多语句安全:用do-while(0)包裹,避免破坏if等结构的语法。

缺点:

      优点蛮多的: 不用建立函数栈帧,直接替换嘛,可读性也会高一些,以及蛮大程度可以解决c语言代码复用的情况。 也就是c++模板类嘛!但是缺点有时候致命: 可维护性在运行阶段的维护巨差,因为宏在预处理阶段就被替换了你看到的的就是一些值和或者一些代码段,有时候你会惊奇这个家伙哪里的,这是在干嘛? what?  所以渐渐的有些场景我们尽量会使用enum常量啊 const全局变量来替代我们的宏, : 场景:定义一些常量 ,开关啊等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/917521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第九天 |151.翻转字符串里的单词、 LCR 182. 动态口令、28. 实现 strStr()、459.重复的子字符串

151.翻转字符串里的单词 思路:前去头空格,再去尾空格,然后依次取出所有的单词,然后用一个字符串接受,然后从放入的数组,倒置输出。难在去空格细节,我是内置for来进行去空格的func reverseWords(s string) strin…

郑州企业排名百度seo排名报价

我们在打印字符时,通常都不用指定字符显示的坐标位置,大家也没觉得有什么奇怪,原因是字符是在当前光标的位置处显示的,而且光标的位置会一直更新顺延,我们的字符一直跟着光标走,似乎光标就是字符的导航一样…

新手做网站视频讲解购买域名和网站

到底计算机考研408是怎么样的。第一,什么学校考408,大部分985和少部分211第二,难度怎么样,确实难度很大,我没考过其他工科,不知道是不是最难。但是我想说的是考过的大部分都说特别难,一般考890不…

广州建外贸网站公司wordpress 文章密码

一辆49cc的二冲程摩托车仅需561元。 购买125的组装车不会超过1元,购买250品牌发动机的组装车不会超过4000元。 购买一辆名牌摩托车大约需要4000到10000元。 花一万到两百万多就能买到一辆像样、动力强劲、能玩的炫酷摩托车。 哈哈,就看你想要什么了&…

当日总结(课后作业2)

1.public class EnumTest { public static void main(String[] args) {Size s=Size.SMALL;Size t=Size.LARGE;//s��t����ͬһ������System.out.println(s==t); ////��ԭʼ����������System.o…

Codeforces Global Round 29 (Div. 1 + Div. 2) A~E

A - Shortest Increasing Path 思维。 当 \(y>x\) 时,可以走 \(x\rightarrow y\) 两步即可;\(x \ge y + 2\) 时,可以走 \(1 \rightarrow y \rightarrow y + 1\) 三步即可,其余无解。点击查看代码 #include <…

AI 低代码平台:不止于 “快”,解码技术融合的深层逻辑

在企业数字化转型的赛道上,“AI + 低代码” 的组合正从概念热潮转向实用工具,但多数人对其认知仍停留在 “拖拽组件 + 自动生成代码” 的浅层理解。事实上,这种技术融合本质上是 “工具层标准化” 与 “智能层通用化…

实用指南:【知识拓展Trip Five】寄存器

实用指南:【知识拓展Trip Five】寄存器pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mo…

动态内存管理(2) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

计算机视觉(opencv)实战二十七——目标跟踪 - 教程

计算机视觉(opencv)实战二十七——目标跟踪 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&quo…

P8367 [LNOI2022] 盒

传送门。 神仙题,做了半年。 整体是不好做的,考虑每个\(w_i\)对整体的贡献。记\(s_i=\sum_{i=1}^{i}a_i\),\(d_i=\sum_{i=1}^{i}b_i\),当且仅当\(s_i\neq d_i\)时,才会有货物流通\(i\)号点。所以总体的答案为: \…

蓝桥杯 2025 省 B 题:画展布置 - 题解笔记

蓝桥杯 2025 省 B 题:画展布置 - 题解笔记.md 一、题目核心信息 1. 问题描述 给定 N 幅画作的艺术价值数组 A,需从其中挑选 M 幅并排列成序列 B(长度为 M),目标是最小化评价指标 L,L 的定义为: \[L = \sum_{i=1…

二维坐标下的运算

在二维图中,常常遇到一些需要大量坐标运算的题目,这时可以封装一个Point类,实现坐标高效运算。 // #define LOCAL #include<iostream> #include<queue> #include<map> using namespace std; #define…

凡科建站怎么导出网页网站优化方案和实施

有一个数据库应用程序存在过多的解析问题&#xff0c;因此需要找到产生大量硬解析的主要语句。 什么是硬解析 Oracle数据库中的硬解析&#xff08;Hard Parse&#xff09;是指在执行SQL语句时&#xff0c;数据库需要重新解析该SQL语句&#xff0c;并创建新的执行计划的过程。这…

Polar2025秋季个人挑战赛web-writeup

感觉难度还行polar快递 在登录页面下载备忘录发现用户等级分四个,抓包发现有id=user,改为最高等级的root登录即可获取flag white 常规输入执行命令发现很多符号都被ban了/[;&$"<>?*[]{}()#@!%]/`,发…

题解:P12751 [POI 2017 R2] 集装箱 Shipping containers

cnblogs 题面 第二道根号分治,对初学者来说很友好的一道题。 题意在题面中写的很清楚,这里不多赘述。 思路 先从暴力开始想。 每次暴力的时间复杂度最坏明显是 \(O(n^2)\) 的,因为是类似区间加和最后统计的问题,可…

弱网配置

sudo tc qdisc replace dev eno1 root netem delay 120ms 30ms 25% loss 5% 解除sudo tc qdisc del dev eno1 ingresshttps://blog.csdn.net/2303_78922833/article/details/151372115

网站建设网页设计小江wordpress重写插件

上篇文章《C自动注册的工厂与--whole-archive》提到了--whole-archive选项在自动工厂示例的必要&#xff0c;“貌似也没其他方法了”。 这篇文章介绍另一种可以替代的方式&#xff0c;并分析其优缺点&#xff0c;采用的代码示例同上篇文章。文章最后附代码。 方法介绍 ld链接器…

net网站开发教学视频牌子网排行榜

报告来源&#xff1a;国泰君安&#xff08;訾猛&#xff09;亚马逊以技术为核心驱动力&#xff0c;实现从电商向科技公司的跨越&#xff0c;形成电商、物流、AWS、新零售协同发展的完整生态圈。亚马逊从1995年开始为用户提供线上商品&#xff0c;从一家网上书店发展成全品类电商…

选择网站做友情链接的标准一般是wordpress点击折叠展开内容

Java概况 JavaSE是java分类中的标准版&#xff0c;是刚接触java要学习的基础知识。 JavaEE是java分类中的企业版&#xff0c;是java中的高级&#xff0c;涉及到的知识广泛。 JavaME中M是Micro的缩写&#xff0c;用在嵌入式等电子设备中。 Java软件工程师&#xff1a;通过Ja…