美文网首页C语言C语言笔记
【C语言学习】带参宏定义(二)

【C语言学习】带参宏定义(二)

作者: 学以解忧 | 来源:发表于2019-08-29 17:04 被阅读0次

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

    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;
    }
    

    输出结果如下:

    image
    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里的内容如下:
    VbOqwF.md.png
    hello.i里的内容与我们上面分析的一致!关于C程序的编译原理可查看往期笔记:【C语言笔记】C语言编译的过程关于windows系统下使用gcc编译器的方法可参考往期笔记:【C语言笔记】使用notepad++、MinGW来开发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;
    }
    

    程序输出结果如下:

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

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

    带参宏—— clarke变换算法:

    [图片上传失败...(image-37213e-1567069431497)]

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

    image
    这些.h文件都使用宏来封装各种算法:

    image

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

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

    带参宏与函数的区别

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

    VbjIbT.md.png
    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));
    

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

    宏的缺点

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

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


    关注我的微信公众号【嵌入式大杂烩】,回复:C语言,可获取C语言资料。
    关注我的个人博客【我的博客】查看更多笔记

    相关文章

      网友评论

        本文标题:【C语言学习】带参宏定义(二)

        本文链接:https://www.haomeiwen.com/subject/hntwectx.html