C语言宏的高级用法

电磁兼容标准体系解析:从基础到认证的完整指南 本文系统解析了电磁兼容(EMC)标准体系,从基础概念到认证流程提供完整指南。文章阐述了EMC对于产品可靠性与市场准入的重要性,详细拆解了基础标准、通用标准、产品类标准及认证流程四层结构,并结合5G设备与工业控制等实战案例,为工程师提供了从设计初期到认证测试的高效应用策略。 阅读详情

总结一下C语言中宏的一些特殊用法和几个容易出错的地方。由于本文主要参考GCC文档,某些细节在别的编译器可能有细微差别。

宏基础

宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见。基本用法如下:

  1. 标示符别名
#define BUFFER_SIZE 1024

预处理阶段,foo = (char *) malloc (BUFFER_SIZE);会被替换成foo = (char *) malloc (1024);

宏体换行需要在行末加反斜杠\

#define NUMBERS 1, \
                2, \
                3

预处理阶段int x[] = { NUMBERS };会被扩展成int x[] = { 1, 2, 3 };

  1. 宏函数

宏名之后带括号的宏被认为是宏函数。用法和普通函数一样,只不过在预处理阶段,宏函数会被展开。优点是没有普通函数保存寄存器和参数传递的开销,展开后的代码有利于CPU cache的利用和指令预测,速度快。缺点是可执行代码体积大。

#define min(X, Y)  ((X) < (Y) ? (X) : (Y))
y = min(1, 2);会被扩展成y = ((1) < (2) ? (1) : (2));

宏特殊用法

  1. 字符串化(Stringification)

在宏体中,如果宏参数前加个#,那么在宏体扩展的时候,宏参数会被扩展成字符串的形式。如:

#define WARN_IF(EXP) \ 
   do { if (EXP) \
               fprintf (stderr, "Warning: " #EXP "\n");} \ 
   while (0)

WARN_IF (x == 0);会被扩展成:

do { if (x == 0)
    fprintf (stderr, "Warning: " "x == 0" "\n"); }while (0);

这种用法可以用在assert中,如果断言失败,可以将失败的语句输出到反馈信息中

  1. 连接(Concatenation)

在宏体中,如果宏体所在标示符中有##,那么在宏体扩展的时候,宏参数会被直接替换到标示符中。如:

#define COMMAND(NAME)  { #NAME, NAME ## _command }

struct command{
    char *name;
    void (*function) (void);};

在宏扩展的时候

struct command commands[] ={
    COMMAND (quit),
    COMMAND (help),
    ...};

会被扩展成:

struct command commands[] ={
    { "quit", quit_command },
    { "help", help_command },
    ...};

这样就节省了大量时间,提高效率。

几个要注意的地方:

  1. 语法问题

由于是纯文本替换,C预处理器不对宏体做任何语法检查,像缺个括号、少个分号神马的预处理器是不管的。这里要格外小心,由此可能引出各种奇葩的问题,在开发过程中很难定位。

  1. 算符优先级问题

不仅宏体是纯文本替换,宏参数也是纯文本替换。有以下一段简单的宏,实现乘法:

#define MULTIPLY(x, y)  x * y

MULTIPLY(1, 2)没问题,会正常展开成1 * 2。有问题的是这种表达式MULTIPLY(1+2, 3),展开后成了1+2 * 3,显然与我们所期望的不同。

在宏体中,给引用的参数加个括号就能避免这问题。

#define MULTIPLY(x, y) (x) * (y)

MULTIPLY(1+2, 3)就会被展开成(1+2) * (3),优先级正常了。

  1. 分号吞噬问题

有如下宏定义:

#define SKIP_SPACES(p, limit)  \  
    { char *lim = (limit);        \  
         while (p < lim) {            \     
            if (*p++ != ' ') {        \   
                 p--; break; }}}

假设有如下一段代码:

if (*p != 0)
  SKIP_SPACES (p, lim);
else ...

一编译,GCC报error: ‘else’ without a previous ‘if’。原来这个看似是一个函数的宏被展开后是一段大括号括起来的代码块,加上分号之后这个if逻辑块就结束了,所以编译器发现这个else没有对应的if。

这个问题一般用do ... while(0)的形式来解决:

#define SKIP_SPACES(p, limit)    \   
 do { char *lim = (limit);        \
            while (p < lim) {            \   
            if (*p++ != ' ') {        \  
             p--; break; }}}          \   
 while (0)

展开后就成了

if (*p != 0)
    do ... while(0);
    else ...

这样就消除了分号吞噬问题。

这个技巧在Linux内核源码里很常见,比如这个置位宏#define SET_REG_BIT(reg, bit) do { (reg |= (1 << (bit))); } while (0)(位于arch/mips/include/asm/mach-pnx833x/gpio.h)

  1. 宏参数重复调用

有如下宏定义:

#define min(X, Y)  ((X) < (Y) ? (X) : (Y))

当有如下调用时next = min (x + y, foo (z));,宏体被展开成next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));,可以看到,foo(z)被重复调用了两次,做了重复计算。更严重的是,如果foo是不可重入的(foo内修改了全局或静态变量),程序会产生逻辑错误。
所以,尽量不要在宏参数中传入函数调用。

  1. 对自身的递归引用

有如下宏定义:

#define foo (4 + foo)

按前面的理解,(4 + foo)会展开成(4 + (4 + foo)),然后一直展开下去,直至内存耗尽。但是,预处理器采取的策略是只展开一次。也就是说,foo只会展开成(4 + foo),而展开之后foo的含义就要根据上下文来确定了。

对于以下的交叉引用,宏体也只会展开一次。

#define x (4 + y)
#define y (2 * x)

x展开成(4 + y) -> (4 + (2 * x)),y展开成(2 * x) -> (2 * (4 + y))。

注意,这是极不推荐的写法,程序可读性极差。

  1. 宏参数预处理

宏参数中若包含另外的宏,那么宏参数在被代入到宏体之前会做一次完全的展开,除非宏体中含有#或##。

有如下宏定义:

#define AFTERX(x) X_ ## x

#define XAFTERX(x) AFTERX(x)

#define TABLESIZE 1024

#define BUFSIZE TABLESIZE
  • AFTERX(BUFSIZE)会被展开成X_BUFSIZE。因为宏体中含有##,宏参数直接代入宏体。
  • XAFTERX(BUFSIZE)会被展开成X_1024。因为XAFTERX(x)的宏体是AFTERX(x),并没有#或##,所以BUFSIZE在代入前会被完全展开成1024,然后才代入宏体,变成X_1024。

实际应用

宏的应用比较广泛,下面以实际项目中UT单元测试中宏的应用作为例子:

/* UT返回值的判断 */
#define UT_STUB_VAR_JUDGE(func_name)\
do\
{\
    if (g_fail_##func_name##_flag)\
    {\
        if (g_fail_##func_name##_times == 0)\
        {\
            return g_fail_##func_name##_ret;\
        }\
        g_fail_##func_name##_times--;\
    }\
}while(0);
Motoman机器人离线编程——Motocom32开发简介 欢迎关注Jungle的Motoman机器人离线编程专栏系列文章: Motoman机器人离线编程——Motocom32开发简介 Motoman机器人离线编程——Visio Studio环境配置 Motoman机器人离线编程——Motocom32库函数的使用与封装 Motoman机器人离线编程——Motocom32参考文献及注意事项 Motoman机器人离线编程——MotocomES介绍 ... 阅读详情

相关推荐

AR Tag 与 ROS:从基础部署到机器人视觉定位实战

本文详细介绍了AR Tag技术在ROS环境下的部署与应用,涵盖从基础原理到机器人视觉定位的实战技巧。通过分析AR Tag的特征提取、位姿解算等核心技术,结合ROS工具包`ar_track_alvar`的配置优化,帮助开发者实现高精度的视觉定位系统。文章还分享了深度相机配置、坐标转换及性能优化等实用经验,适用于仓储机器人、物流分拣等场景。

weixin_33146151的博客 181

4.3 C语言的高级用法以及易错点

从上面结果可以看出方式1 方式2是一致的: 也就是说方式2中,数组直接给值是可行的。 void *则为“无类型指针”,void *可以指向任何类型的数据。void真正发挥的作用在于:    (1) 对函数返回的限定;    (2) 对函数参数的限定。    众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。    例如下则会报错: 其中p1 = p2语句会编译出错,提示

oDuanYanGuHong的博客 3608

极空间NAS零基础部署Wiki.js:Docker+PostgreSQL全流程避坑指南

本文提供了一份在极空间NAS上从零部署Wiki.js私有知识库的详细指南。通过Docker容器化技术,结合PostgreSQL数据库,手把手讲解环境准备、核心组件配置、性能优化及安全加固等全流程,并特别针对极空间平台特性提供了避坑要点,帮助用户打造安全、高效、自主可控的知识管理系统。

3a9bq4r8t2y的博客 486

C语言中宏的一些高级用法举例

_mptr是成员 b 的首地址, 现在 减去成员 b 在结构体里面的偏移值,算出来的是不是这个结构体的首地址呀。而宏展开(函函数),是在编译时进行的,展开时是不分配内存,也没有返回值,也没有值传递。(5)宏替换只占用编译时间,不占用运行时间而函数调用占用的是运行时间(分配内存,传递参数,执行函数体)。(2) 用获取的变量类型,定义了一个指针变量 __mptr ,并且将成员变量 b 的首地址赋值给它。(1)函数调用时,先求出实参表达式的值,然后带入形参带参数的宏只进行简单的字符替换;

weixin_52849254的博客 1512

基于STM32与CAN总线的多电机协同控制系统设计与实现

本文详细介绍了基于STM32与CAN总线的多电机协同控制系统设计与实现。该系统采用分布式架构,通过CAN总线实现多节点通信,结合梯形加减速算法和同步控制策略,有效解决了工业自动化中多电机同步性差的问题。文章涵盖硬件设计、通信协议、抗干扰措施及实战调试经验,为工业应用提供高可靠性解决方案。

tech5的博客 970

windows10 + wsl2安装docker

一、安装wsl https://docs.microsoft.com/zh-cn/windows/wsl/install-win10 1.1以管理员身份运行powershell 1.2执行如下命令 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart 1.3重启电脑 1.4 重启电脑后,如果需要安装wsl2, 则安装官方文档继续后续操作,因我...

happytree001的博客 1569

C语言大佬的必杀技---宏的高级用法

c语言宏的高级用法,魔法效果

qq_62989250的博客 9823

YOLOv11 改进 - 注意力机制 | 空间增强注意力SEAM(Spatially Enhanced Attention Module)提升遮挡场景检测鲁棒性

本文介绍了分离与增强注意力模块(SEAM)在YOLOv11中的结合应用。SEAM模块旨在增强面部特征学习能力,特别是处理面部遮挡问题。它采用多头注意力机制强调面部区域、抑制背景区域,第一部分使用深度可分离卷积减少参数并学习通道重要性,通过1x1卷积增强通道联系,利用两层全连接网络融合通道信息,经指数归一化后作为注意力权重与原始特征相乘。我们将SEAM集成到YOLOv11的检测头中,并进行相关注册和配置。实验表明,该方法有助于提升模型检测性能。

魔改工程师的博客 1665

C语言的高级用法

c语言

zhen 2011

【文献笔记】ICCV 2021 | Point Transformer

作者提到,特别适合点云处理,因为自注意力机制是的核心,本质上就是一个集合算子:它对输入元素具有顺序不变性和数量不变性3D点云是无序的,即使改变点云txt文件中的点顺序也不会有任何影响。自注意力机制天然具有排列不变性,因为它通过对所有"点对"的关系进行建模来处理输入,而不依赖于任何特定的输入顺序点云数据通常覆盖了3D空间中的对象或场景,理解这些数据需要捕捉点与点之间复杂的空间关系。自注意力机制能够有效的捕捉这些关系,因为它为每个"点对"赋予一个。

m0_55908255的博客 965

《UDS协议从入门到精通》系列——图解0x23:通过地址读取内存

如何读取ECU中指定内存地址的数据?图解UDS 0x23服务带你快速上手!!

车载系统攻城狮 2623

C语言的高级用法学习总结

char sex;int age;int age;代码区全局变量与静态变量区局部变量区,即栈区动态存储区,即堆区。

qq_59994663的博客 365

全覆盖路径规划开源项目Clean-robot-turtlebot3原理及流程概括总结

本系列文章主要介绍全覆盖路径规划开源项目Clean-robot-turtlebot3的相关内容,包含如下四篇文章,分别介绍了开源项目Clean-robot-turtlebot3流程的概括总结、ROS坐标系常用坐标及其变换、Clean-robot-turtlebot3关键函数解析等内容。

慕羽★的博客 6103

c语言宏高级用法,C语言中的宏的高级用法

引文:以下内容引自高焕堂老师的《》一书。认识C的对象与类类的用途类的目的是﹕创造新数据型态。为那描述自然界的万事万物,必须有各式各样的数据型态,才足以充分贴切地表达自然界的静态与动态的美。C只提供数种基本数据型态,欲表达人类社会或大自然的景象,实在并不足够。然而C加上“类”(Class)概念之后,就很容易解决这个问题了。它让程序师定义与创造自己心爱的数据型态来描述心中所想的、眼睛所看到的任何自然界...

weixin_32797081的博客 767

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

C语言中宏(Macro)的高级用法:中英双语

C macros, whether simple or advanced, are a core tool for metaprogramming, conditional compilation, and code reuse.

阿正的梦工坊 5467

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

C语言 宏 高级用法

C语言宏高级用法 [总结] 1、前言     今天看代码时候,遇到一些宏,之前没有见过,感觉挺新鲜。如是上网google一下,顺便总结一下,方便以后学习和运用。C语言程序中广泛的使用宏定义,采用关键字define进行定义,宏只是一种简单的字符串替换,根据是否带参数分为无参和带参。宏的简单应用很容易掌握,今天主要总结一下宏的特殊符号及惯用法。   (1)宏中包含特殊符号

746573746D616E3030 350

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

C语言 宏的高级用法

1. 宏参数里的#号 在一个宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组。简化理解:#是“字符串化”的意思,出现在宏定义中的#是把跟在后面的参数转换成一个字符串 #define ERROR_LOG(module) fprintf(stderr,"error: "#module"\n")ERROR_LOG(“add”); 转换为 fprintf(stderr,”error:

专注大规模数据处理 1471

C语言宏高级用法

C语言宏高级用法 [总结] 1、前言     今天看代码时候,遇到一些宏,之前没有见过,感觉挺新鲜。如是上网google一下,顺便总结一下,方便以后学习和运用。C语言程序中广泛的 使用宏定义,采用关键字define进行定义,宏只是一种简单的字符串替换,根据是否带参数分为无参和带参。宏的简单应用很容易掌握,今天主要总结一下宏 的特殊符号及惯用法。  (1)宏中包含特殊符号:#、

samdyli先生的专栏-wx:lisamdy 1051

C语言宏高级用法背后技术详解

宏定义是C语言预处理器的核心功能之一。它允许我们将一段代码或值赋予一个特定的名称,然后在代码中使用这个名称来代替原始代码或值。在编译之前,预处理器会自动将宏名称替换为其对应的代码或值。(a) : (b))在上面的例子中,我们定义了一个名为PI的宏,它代表圆周率的值;同时定义了一个名为MIN的宏,它接受两个参数a和b,并返回它们中的较小值。本文第一部分介绍了C语言宏的基础知识,包括宏定义、宏替换和宏参数。通过这些基础概念,我们可以开始探索宏的高级用法,并利用它们来编写更加高效、灵活的代码。

极客代码 1407

c语言宏高级用法,C语言宏高级用法 [总结]

1、前言今天看代码时候,遇到一些宏,之前没有见过,感觉挺新鲜。如是上网google一下,顺便总结一下,方便以后学习和运用。C语言程序中广泛的使用宏定义,采用关键字define进行定义,宏只是一种简单的字符串替换,根据是否带参数分为无参和带参。宏的简单应用很容易掌握,今天主要总结一下宏的特殊符号及惯用法。(1)宏中包含特殊符号:#、##.(2)宏定义用do{ }while(0)2、特殊符号#、##(...

weixin_42447681的博客 588

C语言宏高级用法 [总结]

1、前言    今天看代码时候,遇到一些宏,之前没有见过,感觉挺新鲜。如是上网google一下,顺便总结一下,方便以后学习和运用。C语言程序中广泛的使用宏定义,采用关键字define进行定义,宏只是一种简单的字符串替换,根据是否带参数分为无参和带参。宏的简单应用很容易掌握,今天主要总结一下宏的特殊符号及惯用法。   (1)宏中包含特殊符号:#、##. (2)宏定义用do{ }w...

phenixyf的专栏 461

c语言宏高级用法,C语言宏的高级用法

#define START_THREAD(name,param) start_thread(get_thread_idx(#name), param)#define JOIN_THREAD(name) join_thread(get_thread_idx(#name))#define CHANGE_THREAD_PRIO(name, priority) change_thread_prio(get...

weixin_29543939的博客 544
上一篇: GDB调试基本使用方法
下一篇: C内存对齐
52coder
博客等级 码龄17年 87粉丝 128原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值