C语言宏的高级用法

树莓派烧录验证失败:跳过验证步骤实现成功启动的实用技巧 本文针对树莓派烧录系统时常见的验证失败问题,提供了跳过验证步骤实现成功启动的实用技巧。通过分析Windows与Linux文件系统差异导致的SD卡验证报错,指导用户在烧录完成后立即关闭验证窗口,或使用BalenaEtcher等第三方工具规避问题,确保树莓派正常启动。 阅读详情

总结一下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);
ubuntu18.04 升级Ubuntu 20.04 Ubuntu 18.04升级20.04指南:首先备份重要数据,更新当前系统并检查新版本可用性(sudo do-release-upgrade -c)。确认升级后执行sudo do-release-upgrade,按提示完成操作。升级过程需耐心等待,完成后重启系统。注意:升级前务必备份数据,确保系统为最新版本,以降低风险。升级后即可使用Ubuntu 20.04系统。 阅读详情

相关推荐

kettle实验十一:没有数据流入时停止操作

【实验目的】 1.利用Kettle的“阻塞数据直到步骤都完成”,“检测空流”,“中止”等组件,实现Kettle转换流程的控制。 2.熟练掌握“阻塞数据直到步骤都完成”,“检测空流”,“中止”等组件的使用,实现Kettle转换流程的控制。 【实验原理】 “检测空流”组件可以检测所在转换的输入数据,如果整个转换没有输入数据,通过“中止”组件终止整个转换的流程。在存在数据输入的情况,为了能让数据输出,需要和“阻塞数据直到步骤都完成”配合使用。当所有数据都已经从上个步骤“字段选择”读取完毕后,通过“阻塞数据直到步骤

weixin_44727274的博客 3847

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

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

oDuanYanGuHong的博客 3607

【目标检测】yoloV5算法详解

一、与yoloV4相比,yoloV5的改进 输入端:在模型训练阶段,使用了Mosaic数据增强、自适应锚框计算、自适应图片缩放 基准网络:使用了FOCUS结构和CSP结构 Neck网络:在Backbone和最后的Head输出层之间插入FPN_PAN结构 Head输出层:训练时的损失函数GIOU_Loss,预测筛选框的DIOU_nns 二、yoloV5结构框架 CBL:CBL模块是由Conv+BN+Leaky_relu激活函数组成 Res unit:借鉴ResNet中的残差结构,用来构建深层网络,CBM是

qq_40373651的博客 2万+

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

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

qq_62989250的博客 9823

USRP N210与MATLAB连接实战:从驱动安装到信号接收

本文详细介绍了USRP N210与MATLAB连接的完整流程,涵盖驱动安装、网络配置、支持包安装及信号接收实战。重点解决了安装过程中的常见错误和硬件连接问题,并通过MATLAB代码示例展示了如何配置接收器、处理信号数据,帮助用户快速实现从设备对接到信号分析的全过程。

blue的专栏 817

horizon之为connection服务器配置证书

打开connection1服务器(connection2 同connection1)再输入connection1的IP地址 可通过域名和IP地址进行登录。这时候用域名登录就发现证书已经没有警告了。win+R 调出“运行”,输入mmc。connection2 同上。如果还有报错请重启一下服务器。打开connection1。可以把这个先删掉 没用。打开DC_01服务器。右键选择证书颁发机构。友好名称只能是vdm。重启之后就没有报错了。

送个祝福给小豪的博客 2957

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

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

weixin_52849254的博客 1512

大数据之父_BIM先驱Charles (Chuck) M. Eastman逝世——致敬“BIM之父”

原标题:BIM先驱Charles (Chuck) M. Eastman逝世——致敬“BIM之父”BIM研究的先驱、佐治亚理工教授Charles (Chuck) M. Eastman于本周一(11月9日)在其Atlanta家中逝世。特转韩国Yonsei大学Ghang Lee教授的讣文以作纪念。过世原因未说明。BIM的出现是与当今时代科技的发展分不开的。在信息时代建筑发展面临的挑战巨大:建筑物越建越高...

weixin_39941262的博客 3302

C语言的高级用法

c语言

zhen 2011

【ROS2实体机械臂驱动】封装珞石ROS2驱动库ros2_rokae_driver_lib

这一篇文章将封装为,和之前一篇文章《ROS2 编译、打包、封装三方库(Knet为例)_ros插件编译成静态库-优快云博客》写的封装,区别在于优化了cmake dependencies和include等属性和宏定义,同时对于多库内部依赖的情况做了特定处理,更加方便外部调用。其中珞石的编译参考博主之前的文章rokae xCoreSDK C++测试使用,这里不再做赘述。通过这篇文章对于如何打包机械臂厂家提供的三方库做了进一步说明,方便用于不同机械臂厂家的三方库封装调用。!!

机器人梦想家 Bing Lee 1396

【经验&技巧】如何在ADS中导入示波器的时域波形?

从示波器中导出的时域波形数据,需要按照固定的格式进行修改,并通过Display dataset对话框导入ADS。

一只豌豆象的博客 1571

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

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

qq_59994663的博客 364

VB.net:VB.net编程语言学习之ADO.net基本名称空间与类的简介、案例应用(实现与SQL数据库编程案例)之详细攻略

ADO.net有六个基础名称空间,除此之外,每个新的数据提供程序还可以有自己的名称空间,如Oracle。在.net中就提供了System.Data.OracleClient名称空间(用于Microsoft建立的Oracle数据库提供的程序)ADO.net体系结构的另一个核心元素是.Net数据提供程序(Data Provider),它的组件用于进行数据处理(和DataSet的数据访问功能相对应)。:是ADD.net核心名称空间,它包含所有数据提供程序使用的类,这些类表示表、列、行,还有DataSet类;

头部AI社区如有邀博主AI主题演讲请私信—心比天高,仗剑走天涯,保持热爱,奔赴向梦想!低调,专注,谦虚,自律,反思,成长,还算比较正能量的博主,公益免费传播…内心特别想在AI界做出一些可以推进历史进程影响力的技术(兴趣使然,有点小情怀,也有点使命感呀 1731

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

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

weixin_32797081的博客 767

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

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

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

阿正的梦工坊 5462

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

C语言 宏 高级用法

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

746573746D616E3030 350

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

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、付费专栏及课程。

余额充值