C语言中的宏

宏定义又称为宏替换,简称“宏”,在C语言预处理阶段被处理,编译器会根据宏定义进行文本替换。这样做的好处有许多,它可以为程序员在编程时提供方便,并能在一定程度上提高程序的运行效率。

本文将通过一部分场景,来学习一些和宏相关的知识。

#define

  1. 采用宏定义一些常用常量,避免在代码中出现magic number:

#define PI 3.1415926
#define ERROR -1
#define NULL (void*)0
#define EOF (-1)
......

2. 用宏定义避免头文件重复引用,很常见,等价于#pragma once

#ifndef _XXXX_H_
#define _XXXX_H_
......
#endif

这样做相较于#pragma的优点是,所有的编译器都能达到想要的效果(有的编译器可能不支持#pragma once),缺点是,宏定义重复可能带来问题。

3. 一些常用的简单函数,使用宏函数实现,防止出错,也省得打字。宏函数名和括号之间不能有空格!

#define ADD(a,b) (a+b)
#define RND8( x )       ((((x) + 7) / 8 ) * 8 ) 返回一个比x大的8的倍数
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 得到某个结构体成员相较于结构体头部的偏移
......

4. 通过宏,可以用代码段进行替换,为了代码美观,可以使用\换行,宏函数可以具有返回值,最后一行的值为返回值。同时,宏之间可以嵌套调用,下例中就嵌套调用了上例中的offsetof:

内核中相当著名的container_of宏定义实现:
它具有返回值,最后一行返回了该member所在的结构体的内存中起始位置
#define container_of(ptr, type, member) ({      \
  const typeof(((type *)0)->member) * __mptr = (ptr);  \
  (type *)((char *)__mptr - offsetof(type, member)); })

用法是:

(type*) p = container_of(成员指针, 成员结构体类型, 成员名)

5. 很魔幻的用法,可以将输入转换为单双引号字符串或者将输入串联起来。

##把输入串联起来

#define CONN(a, b) a##b

C语言中的宏_第1张图片

#@把输入转换为单引号字符串

#把输入转换为双引号字符串

#define CONN(a, b) #@a###b

C语言中的宏_第2张图片

这个魔幻的操作有什么用途呢?

比如我们想要对磁盘命名 disk1 disk2 disk3,它们是字符串,就可以用宏定义的方式,迅速转换,快速填充。

6. 一些编译器的特殊用法,比如可变长参数表,编译器优化等:

ANSI标准说明了五个预定义的宏名。它们是:
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
__VA_ARGS__与##__VA_ARGS__(##__VA_ARGS__可以在可变参数个数为0时,去掉最后的逗号)

注意!!

在宏定义使用时,也有一些容易出问题的地方需要注意。

  1.  善用小括号:

不使用小括号,可能带来错误:

#define add(a, b) a+b
当计算:
add(2, 2) * add(2, 2)
经过编译器预处理:
2 + 2 * 2 + 2
导致错误。
因此正确写法为:
#define add(a, b) (a + b)

2. do{}while(0) 有妙用:

这个循环本质上只会执行一次,但是采用这样的写法的作用是首先使得宏定义替换后的代码在一个独立代码段中,局部变量可以重复定义不受影响,其次即使传入参数为空,编译器也不会警告。

同时,对于以下情况,能避免错误:

#define cond(arg) if(arg) dosomething;
假设该宏被用在
if(condition)
cond(A)
else 
B
展开后:
if(condition)
if(A) dosomething;
else
B
这个if-else的配对关系就被“误解”了导致出错。

那么为什么要用do...while(0)呢?似乎用一个大括号{}也能解决问题。目的是防止出现错误的分号:

#define cond(arg) {if(arg) dosomething;}
假设该宏被用在
if(condition)
cond(A);
else 
B
展开后:
if(condition)
{if(A) dosomething;};
else
B
会发现,多了一个分号,导致编译错误。
所以,使用do-while结构,形成一个语法单元,能避免此类错误
同时,无效的循环基本都会被编译器优化,不会造成性能下降。


因此,正确写法:
#define cond(arg) do{if(arg) dosomething;}while(0)

你可能感兴趣的:(c语言,开发语言)