C语言 | 学会使用带参宏,提高编程基础(二)_带参宏指令-程序员宅基地

技术标签: 宏函数  C/C++  编程基础  C语言  带参宏  嵌入式  

1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。关注微信公众号【嵌入式大杂烩】,回复1024,即可免费获取!

基本概念

带参宏在我们的嵌入式编程中使用得非常多,其定义如下:

define 标识符(参数列表) 字符序列

其中参数列表中的参数之间用逗号分隔,字符序列中应包含参数表中的参数。在定义带参数的宏时,宏名标识符与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的宏定义。

并且,字符序列与其每一个参数必须用括号扩起来,否则该宏定义可能会产生二义性 。下面举个简单的例子,定义一个求平方的宏函数:

#include <stdio.h>
#define SQUARE(a) a*a // 不严谨的写法
int main(void)
{
    
  int x = 5;
  int res = 0;
  res = SQUARE(x+2);
  printf("res = %d\n", res);
  return 0;
}

输出结果如下:
在这里插入图片描述

res变量的输出结果为17,与我们期望的res = 49;相差甚远!这就是因为我们不给字符序列中的宏参数加括号的原因,产生了歧义。程序生成可执行程序之前的预处理过程中把SQUARE(x+2)替换成了x+2*x+2,因此当x=5时res的结果为17。我们可以使用命令gcc -E hello.c -o hello.i进行预处理,然后查看经过预处理得到的文件hello.i的内容,hello.i里的内容如下:

hello.i里的内容与我们上面分析的一致!关于C程序的编译原理可查看往期笔记:【本质】你知道C语言编译的过程吗?关于windows系统下使用gcc编译器的方法可参考往期笔记:使用Notepad++来开发C程序

以上程序严谨的求平方的宏函数的定义如下:

#include <stdio.h>
#define SQUARE(a) ((a)*(a)) // 严谨的做法
int main(void)
{
    
  int x = 5;
  int res = 0;
  res = SQUARE(x+2);
  printf("res = %d\n", res);
  return 0;
}

程序输出结果如下:
在这里插入图片描述

可见,这才是我们要的正确结果。

带参宏到底有多重要,看看TI的一些官方例程就知道,其把很多算法使用带参宏封装起来,用户就可以很方便的使用。

带参宏—— clarke变换算法:

带参宏—— PI调节器算法:

其他的宏来封装的算法:

同样,ST官方固件库中也大量使用带参宏:

可见带参宏定义的重要性!除此之外,通过以上宏定义,可发现很多宏定义分行时,其行后都加上反斜杠\进行分隔,这也是需要注意的细节。

带参宏与函数的区别

查看以上带参宏,我们发现带参宏似乎与函数似乎长得很像,它们之间有什么区别和联系呢?TI为什么要使用宏来对一些算法进行封装呢,难道使用函数来封装不可以吗?答案是可以的:

在这里插入图片描述

TI也说了,使用者可以很方便地把这些算法宏转换成一些函数。换句话说就是你可以使用宏定义,也可以使用函数。那么,什么时候封装成宏定义比较好,什么时候封装成函数比较好呢?

以下内容参考文章:
http://blog.sina.com.cn/s/blog_861912cd0100tc94.html
下面,先看一下带参宏与函数的一些区别,举个例子,比较两个数或者表达式大小:

(1)带参宏的方式:

#define MAX(a,b) ((a)>(b)?(a):(b))

(2)函数封装的方式:

int max(int a, int b)
{
    
  return (a > b ? a : b);
}

很显然,我们不会选择用函数来完成这个任务,原因有两个:

(1)首先,函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。这种开销不仅会降低代码效率,而且代码量也会大大增加,而使用宏定义则在代码规模和速度方面都比函数更胜一筹;

(2)其次,函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型的比较函数。反之,上面的那个宏定义可以用于整形、长整形、单浮点型、双浮点型以及其他任何可以用“>”操作符比较值大小的类型,也就是说,宏是与类型无关的。

除此之外,宏与函数的不同点还有:宏是在预处理阶段展开,占用的是编译时间,函数实在程序运行时调用的,占用的是程序运行的时间;宏参数没有类型说明,也没有返回值的概念。

和使用函数相比,使用宏的不利之处在于每次使用宏时,一份宏定义代码的拷贝都会插入到程序中。除非宏非常短,否则使用宏会大幅度增加程序的长度。

还有一些任务根本无法用函数实现,但是用宏定义却很好实现。比如参数类型没法作为参数传递给函数,但是可以把参数类型传递给带参的宏。

看下面的例子:

#define MALLOC(n, type)\
  ((type *) malloc((n)* sizeof(type)))

利用这个宏,我们就可以为任何类型分配一段我们指定的空间大小,并返回指向这段空间的指针。我们可以观察一下这个宏确切的工作过程:

int *ptr;
ptr = MALLOC(5, int);

将这宏展开以后的结果:

ptr = (int *)malloc((5) * sizeof(int));

这个例子是宏定义的经典应用之一,完成了函数不能完成的功能,但是宏定义也不能滥用,通常,如果相同的代码需要出现在程序的几个地方,更好的方法是把它实现为一个函数。

宏的缺点

  • 宏可能会大幅度增加程序的长度。
  • 宏是没法调试的。
  • 宏由于与类型无关,也就不够严谨。
  • 宏可能会带来运算符的优先级问题,即二义性。

以上就是关于带参宏的一些总结,如有错误,欢迎指出!

1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。关注微信公众号【嵌入式大杂烩】,回复1024,即可免费获取!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhengnianli/article/details/88879697

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法