5

c语言入门26,宏定义的使用

 3 years ago
source link: https://blog.popkx.com/introduction-to-c-language-26-use-of-macro-definition/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前面几节较详细的介绍了 C 语言指针的用法,尤其在上一节,我们讨论了如何利用 C 语言实现“伪类”。有朋友评论说这样没有意义,的确,在上一节的例子中的确没有什么意义,伪类”甚至还让本来简单的代码变得更繁杂了。但是,在较大的复杂项目中,使用 C 语言“伪类”封装还是司空见惯的

721f8a1e9987ec6ea71e0948293ca151.png

事实上,linux 内核源代码中有大量使用“伪类”的地方,本节不再讨论“伪类”。

谈到较大的项目,就不得不提一下“宏定义”了,较大的项目都会用大量的宏定义来组织代码,随便找一个开源项目,打开它的源代码头文件,看看能发现多少宏定义。你可能用过 #define N 20 这种宏定义,看起来宏定义只不过是做个替换而已,其实里面有比较复杂的规则。本节打算谈一谈宏定义,是因为宏定义在 C 语言中非常重要,也非常有用,有时有些实现甚至非宏定义莫属。

函数式宏定义

像 #define N 20 这种宏定义称为“变量式”宏定义,N 可以像变量一样使用,但是 N 属于常量表达式。实际上,还有一种可以像函数一样使用的宏定义,可称之为“函数式宏定义”,请看如下代码:

#define        MIN(a, b)       ( (a)<(b)?:(a):(b) )
x = MIN(3&0x0f,  5&0x0f)

将 x = MIN(3&0x0f, 5&0x0f) 表达式展开,得:

x = ( (3&0x0f)<(5&0x0f)?(3&0x0f):(5&0x0f) )

d = a?b:c 这个表达式的意思是,if(a) d=b; else d=c;

66be153bdc808c4675d593ee347e20bd.png

可以看出,函数式宏定义 MIN 可以像函数一样使用,两个实参被替换到宏定义形参 a 和 b 的位置了。应当注意,函数式宏定义和真正的函数是有区别的:

  • 函数式宏定义的参数没有类型,预处理时不做参数类型检查,所以使用时要确保类型正确。
  • 函数式宏定义本身不会被编译为函数,调用时就是直接把宏定义替换过来,而不是简单的几条传参和 call 指令,所以函数式宏定义编译生成的目标会比真正的函数大。
  • 定义函数式宏定义要非常小心,如果 MIN 定义成 #define MIN(a, b) ( a<b? a:b ),则 x = MIN(3&0x0f, 5&0x0f) 展开就成了 x = ( 3&0x0f<5&0x0f?3&0x0f:5&0x0f ),运算符的优先级就错了,不会得出正确结果。读者思考一下,外层括号能否省略?
  • 因为调用函数式宏定义就是简单替换,所以如果 MIN(i++, j++) 时,展开就是 ( (i++)<(j++)?(i++):(j++) ),i和j自加的次数是不确定的。如果是 MIN 真正的函数,则 i 和 j 确定是只自加一次。
781b25194d3348a0e4a0af40be4293e3.png

在 linux 内核中,函数式宏定义通常使用 do{...}while(0) 包裹:

#define do_something(i) \
            do{                         \
                    i ++;               \
                    printf("i = %d\n", i);  \
            }while(0)

为什么呢?请看下面这个例子,就明白了:

if(i>5)
    do_something(i);
else
    printf("test\n");

如果没有使用 do{...}while(0) 包裹,把 do_something 展开后,变为:

if(i>5)
    i++;
    printf("i = %d\n", i);
else
    printf("test\n");

printf("i = %d\n", i); 这句没有被包含在 if 判断语句里,而且 else 语句并没有与 if 配对,所以编译会报错。那能否在宏定义时,使用 {} 包裹呢?还是上面的例子,使用 {} 包裹展开后:

if(i>5)
{
    i++;
    printf("i = %d\n", i);
};          // ;
else
    printf("test\n");

虽然 printf("i = %d\n", i); 这句被包含在 if 判断语句里了,但是 do_something(i); 最后的 “;”会被展开到 {} 后面,这样表示 if 的判断结束了,else 依然没有与 if 配对,还是会编译报错。那 do_something(i); 后面的这个“;”不写不就行了吗?的确,不写就没有错误了,但是不写 ";",看起来就不像函数调用了,对不?整个语句显得怪怪的,哪天顺手一加,就又错了。

a6cd1ef4134fbe94e6d5d027c84c5a39.png

C 语言宏定义的 ## 运算符

请看实例:

#define test(a, b)    a##b
test(he, llo)

test(he, llo) 预处理后,就相当于 hello。再请看:

#include <stdio.h>
#define test(a) print##a
void print1()
{
    printf("hello 1\n");
}
void print2()
{
    printf("hello 2\n");
}
int main()
{
    test(1)();
    test(2)();
    return 0;
}

显然,“##”是一个特殊的拼接符,说它特殊是因为它拼接的并不是字符串,以上代码执行结果如下:

c77b8c5523e59c452e83acdf810d9816.png

利用 define 也可以实现一些不定参数的函数式宏定义,请看:

// 开发时
#define debug(format, ...)  printf(format, ## __VA_ARGS__)
// 发行时
#define debug(format, ...) 

这么定义以后,debug 就可以像 printf 一样使用了。在开发阶段,需要打印出信息时,使用 debug 代替 printf。这么做的好处是,开发完毕不再需要打印信息时,只需要简单的把 debug 定义为空即可,而无需再去挨个删除 printf。以后还想打印信息,再把 printf 添加回来即可。

有时候,函数式宏定义可以做到函数难以实现的事

现在的 C 语言及其编译器支持了很多有趣的关键字,例如:

__FUNCTION__ 表示函数名
__LINE__ 表示所在行号
等等

请看如下代码:

#include <stdio.h>
int main()
{
    printf("%s %d\n", __FUNCTION__, __LINE__);
    return 0;
}

编译器在编译时,会自动的把 "FUNTION" 和“LINE”替换为函数名和行号,这样就不用程序员逐个手动输入了,而且代码的可移植性也更强。

44fa5ef8888de6f8b20dce836e1efc13.png

为了更方便的输出当前位置,我们可以定义函数式宏定义:
#include <stdio.h>

#define location() \
        do{         \
            printf("fun: %s, line: %d\n", __FUNCTION__, __LINE__);\
        }while(0)
void test()
{
    location();
}
int main()
{
    test();
    location();
    return 0;
}

打印出位置是有用的,它能帮助我们在大型项目的复杂代码中快速的找到出错的函数,出错的行号。(类似于 LINE 的关键字还有一些,留给读者自行查阅了)

cc8d3fd1de5b59afeb2420456eca411d.png

在 windows 环境中开发,各种 IDE 把程序员照顾的很好。但是有些错误,即使是 IDE 也无法定位,以后会看到这样的例子。另外,嵌入式开发中,可不一定有合适的 IDE 给程序员使用。

location 是一个函数式宏定义,所以调用它,就相当于把代码展开到调用位置,所以它可以打印出 test 中的位置,也可以打印出 main 中的位置。如果 location 是一个真正的函数,输出结果就不同了,请看:

a15b3cfb381aeb45717bcb7ffb82977b.png

原因留给读者自己分析了。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK