C语言宏定义中的迷惑行为

原创 嵌入式软件实战派 2021-10-07 12:31

↑点击上方蓝色字体,关注“嵌入式软件实战派”获得更多精彩内容。



1. 问题

以下这段代码运行后输出什么结果?

#define f(a,b)  a##b  
#define g(a)   #a  
#define h(a)   g(a)  
printf("h(f(1,2))-> %s, g(f(1,2))-> %s\n", h(f(1,2)), g(f(1,2)));

先上答案:

h(f(1,2))-> 12, g(f(1,2))-> f(1,2)

也许你跟我一样,满脑子的“为什么“?

2. 宏定义中的#和##

(对于已经理解这两个符号的同学,建议跳过)

首先说一下:

  • #是将内容字符串化

  • ##是连接字符串

直接在tutorialspoint找两个例子说明下:

例1:

#include <stdio.h>

#define message_for(a, b) \
  printf(#a " and " #b ": We love you!\n")

int main(void) {
  message_for(Carole, Debra);
  return 0;
}

输出结果是:

Carole and Debra: We love you!

例2:

#include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void) {
  int token34 = 40;
  tokenpaster(34);
  return 0;
}

输出结果是:

token34 = 40

tokenpaster(34);这个通过预处理后就变成了printf ("token34 = %d", token34)

到这来,我想大家基本上理解这###是什么意思了吧。

3. 宏替换

回到文章开始的问题,为什么h(f(1,2))g(f(1,2))的结果是不一样的?这就要看宏替换的规则是什么了。

这个题目在网上能搜出一堆博文来,很多都没讲透。于是,我翻C99标准《ISO/IEC 9899:1999 (E)》,其中有一个规则是这样的:

After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

这段话不好理解,以前在学校上学的阅读理解能力就要发挥出来了,我大致将其拆解来讲解下:

  1. 首先要identify这个是function-like macro,即识别出这是一个函数式宏;

  2. 处理函数式宏里面的参数parameter,这里有个条件,在替换这些参数宏的时候,除非遇到###这样的东西,否则就将这些parameter替换到底;

  3. 待所有的parameter都替换完(或者###处理完),预处理器还会对整个宏剩下的继续处理,因为这个function-like macro会替换出一些新东西,例如h( f(1,2) )--> g(12)g(12) --> "12"

于是,按以上这个套路将上面的问题解析一遍:先看h(f(1,2))

  1. h(f(1,2))预处理先找到h这个function-like macro

  2. 然后处理其parameter,即将f(1,2)替换成12,即得到g(12)

  3. 继续往后走,得到12

再看g(f(1,2))

  1. g(f(1,2))预处理先找到g这个function-like macro

  2. 然后替换parameter,发现替换后遇到#了,即得到#f(1,2)

  3. 再然后也没有然后了,将这个#f(1,2)变成了字符串f(1,2)了,因为预处理遇到#不会继续了。

宏替换除了以上这条规则,还有很多,有十几条之多,我之前在《基于C99规范,最全C语言预处理知识总结》有讲解,欢迎查阅。

4. 问题延伸

顺着这个问题例子,来做个实验:

    #define ABC"abc"

#define _STR(x) #x
#define STR(x) _STR(x)

char * pc1 = _STR(ABC);
char * pc2 = STR(ABC);

printf("pc1:%s\n",pc1);
printf("pc2:%s\n",pc2);

会输出什么结果?其实不难理解,按照上面的套路可以得到(实际输出的结果):

pc1:ABC
pc2:"abc"

但是,注意啊,这个"abc"是把双引号""都打印出来的哦。或者,可以通过gcc -E来看看预编译后的结果:

# 3 "macro.c"
int main(void)
{
# 14 "macro.c"
char * pc1 = "ABC";
char * pc2 = "\"abc\"";

# ... ...

别着急疑惑,继续往下看(在上面例子基础上加点料,看完你会更疑惑):

    char * pc3 = STR(_STR(ABC));
char * pc4 = STR(STR(ABC));

printf("pc3:%s\n",pc3);
printf("pc4:%s\n",pc4);

你觉得它会输出什么结果?答案是:

pc3:"ABC"
pc4:"\"abc\""

不仅""出来了,连\都输出来了。哈哈,你品你细品!


关注公众号,获得更多精品推送。

嵌入式软件实战派 专注嵌入式软件开发领域知识传授,包括C语言精粹,RTOS原理与使用,MCU驱动开发,AUTOSAR搭建,软件架构方法设计等。
评论
  • 铁氧体芯片是一种基于铁氧体磁性材料制成的芯片,在通信、传感器、储能等领域有着广泛的应用。铁氧体磁性材料能够通过外加磁场调控其导电性质和反射性质,因此在信号处理和传感器技术方面有着独特的优势。以下是对半导体划片机在铁氧体划切领域应用的详细阐述: 一、半导体划片机的工作原理与特点半导体划片机是一种使用刀片或通过激光等方式高精度切割被加工物的装置,是半导体后道封测中晶圆切割和WLP切割环节的关键设备。它结合了水气电、空气静压高速主轴、精密机械传动、传感器及自动化控制等先进技术,具有高精度、高
    博捷芯划片机 2024-12-12 09:16 97浏览
  • 首先在gitee上打个广告:ad5d2f3b647444a88b6f7f9555fd681f.mp4 · 丙丁先生/香河英茂工作室中国 - Gitee.com丙丁先生 (mr-bingding) - Gitee.com2024年对我来说是充满挑战和机遇的一年。在这一年里,我不仅进行了多个开发板的测评,还尝试了多种不同的项目和技术。今天,我想分享一下这一年的故事,希望能给大家带来一些启发和乐趣。 年初的时候,我开始对各种开发板进行测评。从STM32WBA55CG到瑞萨、平头哥和平海的开发板,我都
    丙丁先生 2024-12-11 20:14 86浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-11 17:58 98浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 123浏览
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 120浏览
  • 应用环境与极具挑战性的测试需求在服务器制造领域里,系统整合测试(System Integration Test;SIT)是确保产品质量和性能的关键步骤。随着服务器系统的复杂性不断提升,包括:多种硬件组件、操作系统、虚拟化平台以及各种应用程序和服务的整合,服务器制造商面临着更有挑战性的测试需求。这些挑战主要体现在以下五个方面:1. 硬件和软件的高度整合:现代服务器通常包括多个处理器、内存模块、储存设备和网络接口。这些硬件组件必须与操作系统及应用软件无缝整合。SIT测试可以帮助制造商确保这些不同组件
    百佳泰测试实验室 2024-12-12 17:45 88浏览
  • 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进步,对于图像数据的采集、处理和分析的需求日益增长,这不仅要求我们拥有高性能的相机硬件,还要求我们能够高效地集成和测试各种算法。我们探索了一种多源相机数据采集与算法集成测试方案,能够满足不同应用场景下对图像采集和算法测试的多样化需求,确保数据的准确性和算法的有效性。一、相机组成相机一般由镜头(Lens),图像传感器(Image
    康谋 2024-12-12 09:45 100浏览
  • 全球智能电视时代来临这年头若是消费者想随意地从各个通路中选购电视时,不难发现目前市场上的产品都已是具有智能联网功能的智能电视了,可以宣告智能电视的普及时代已到临!Google从2021年开始大力推广Google TV(即原Android TV的升级版),其他各大品牌商也都跟进推出搭载Google TV操作系统的机种,除了Google TV外,LG、Samsung、Panasonic等大厂牌也开发出自家的智能电视平台,可以看出各家业者都一致地看好这块大饼。智能电视的Wi-Fi连线怎么消失了?智能电
    百佳泰测试实验室 2024-12-12 17:33 90浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 96浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-12 10:13 62浏览
  • 本文介绍瑞芯微RK3588主板/开发板Android12系统下,APK签名文件生成方法。触觉智能EVB3588开发板演示,搭载了瑞芯微RK3588芯片,该开发板是核心板加底板设计,音视频接口、通信接口等各类接口一应俱全,可帮助企业提高产品开发效率,缩短上市时间,降低成本和设计风险。工具准备下载Keytool-ImportKeyPair工具在源码:build/target/product/security/系统初始签名文件目录中,将以下三个文件拷贝出来:platform.pem;platform.
    Industio_触觉智能 2024-12-12 10:27 93浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦