C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。下面对常遇到的宏的使用问题做了简单总结。

关于#和##

在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量 通过替换后在其左右各加上一个双引号。比如下面代码中的宏:

#define WARN_IF(EXP)    do{ if (EXP)    fprintf(stderr, "Warning: " #EXP "/n"); }   while(0)

那么实际使用中会出现下面所示的替换过程:

WARN_IF (divider == 0);被替换为

do {
if (divider == 0)
fprintf(stderr, "Warning" "divider == 0" "/n");
} while(0);

这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。

而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定 是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:

struct command
{
char * name;
void (*function) (void);
};
#define COMMAND(NAME) { NAME, NAME ## _command }
// 然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:
struct command commands[] = {
COMMAND(quit),
COMMAND(help),
...
}

COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。我们还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的。比如:

#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 这里这个语句将展开为:
//  typedef struct _record_type name_company_position_salary;

关于...的使用

...在C宏中称为Variadic Macro,也就是变参宏。比如:

#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)
// 或者
#define myprintf(templt,args...) fprintf(stderr,templt,args)

第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏 中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出 现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:

myprintf(templt,);

的形式。这时的替换过程为:

myprintf("Error!/n",);替换为:

fprintf(stderr,"Error!/n",);

这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:

myprintf(templt);

而它将会被通过替换变成:

fprintf(stderr,"Error!/n",);

很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:

#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)

这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:

myprintf(templt);被转化为:

fprintf(stderr,templt);

这样如果templt合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

错误的嵌套-Misnesting

宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

由操作符优先级引起的问题-Operator Precedence Problem

由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:

#define ceil_div(x, y) (x + y - 1) / y

那么

a = ceil_div( b & c, sizeof(int) );

将被转化为:

a = ( b & c  + sizeof(int) - 1) / sizeof(int);
// 由于+/-的优先级高于&的优先级,那么上面式子等同于:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);

这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:

#define ceil_div(x, y) (((x) + (y) - 1) / (y))

消除多余的分号-Semicolon Swallowing

通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:

MY_MACRO(x);

但是如果是下面的情况:

#define MY_MACRO(x) { /* line 1 */    /* line 2 */    /* line 3 */ }
//...
if (condition())
MY_MACRO(a);
else
{...}

这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:

#define MY_MACRO(x) do {
/* line 1 */    /* line 2 */    /* line 3 */ } while(0)

这样只要保证总是使用分号,就不会有任何问题。

Duplication of Side Effects

这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:

#define min(X,Y) ((X) > (Y) ? (Y) : (X))
//...
c = min(a,foo(b));

这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:

#define min(X,Y) ({ typeof (X) x_ = (X);   typeof (Y) y_ = (Y);   (x_ < y_) ? x_ : y_; })

({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。

转载于:https://www.cnblogs.com/iplus/archive/2013/04/07/4467308.html

C语言宏定义##连接符和#符的使用及其它宏定义注意事项相关推荐

  1. C/C++语言宏定义##连接符和符#的使用

    C/C++语言宏定义##连接符和符#的使用     [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/53113187 (一) ...

  2. 【C++】46.宏定义##连接符和符#的使用

    推荐下面两篇博客: 1.C/C++语言宏定义##连接符和符#的使用 2.c++/c中的##连接符的使用 在c/c++语言中,我们可以使用##来对一些字符进行连接,方便使用,尤其是在宏定义中使用该符号, ...

  3. C/C++宏定义连接符“\”

    在C/C++宏定义中规定,宏定义只允许写在同一行,下一行就不属于这个宏定义了,而实际定义宏过程中,经常会出现较复杂的宏定义,较长,写在一行不易于读懂,需按逻辑关系断行编写,此时需要在行的末尾用连接符& ...

  4. c语言中不带任何修饰符的浮点变量,江苏省计算机等级考试二级C语言笔试辅导题目...

    <江苏省计算机等级考试二级C语言笔试辅导题目>由会员分享,可在线阅读,更多相关<江苏省计算机等级考试二级C语言笔试辅导题目(155页珍藏版)>请在装配图网上搜索. 1.1 江苏 ...

  5. java中有哪几种访问修饰符_Java语言中有4种访问修饰符

    转载:http://wuhaidong.iteye.com/blog/851754 Java语言中有4种访问修饰符 在Java语言中有4中访问修饰符:package(默认).private.publi ...

  6. C语言关键字 数据类型 格式符 修饰符 运算符一览

    文章目录 关键字 数据类型关键字 输出格式符.修饰符 printf 输出格式符 conversion specifier 输出格式符修饰符 modifier 输入格式符.修饰符 scanf 输入格式符 ...

  7. C语言字符输入回车时停止,解决C语言输入单个字符屏蔽回车符的问题

    C语言的scanf()函数在接收输入单个字符时会把上一次输入的回车符号当做这次输入的字符,造成无法正确的输入字符数据.这恐怕是初学C的童鞋门遇到的最头疼的问题了. 今天给大家提供四种解决方法供借鉴. ...

  8. php 如何宏定义,[PHP] PHP源码常用代码中的宏定义

    PHP源码常用代码宏定义: #define 宏名 字符串 #表示这是一条预处理命令,所有的预处理命令都以#开头.define是预处理命令.宏名是标识符的一种,命名规则和标识符相同.字符串可以是常数.表 ...

  9. 计算机硬盘分区和盘符,盘符

    盘符是DOS.WINDOWS系统对于磁盘存储设备的标识符.一般使用26个英文字符加上一个冒号:来标识.由于历史的原因,早期的PC机一般装有两个软盘驱动器,所以,"A:"和" ...

  10. java部分基础知识 (二):计算机组成原理 原码 补码 反码 按位符 移位符 按位与 按位或 按位抑或 非 分析hashMap的put方法原理

    这里写目录标题 引言 符号位 正数的二进制计算 负数的二进制计算 按位符和移位符 按位符 移位符 分析hashMap运算符 byte和char 总结 引言 最近做完一个项目后,我忽然发现自己的基础并不 ...

最新文章

  1. Android典型界面设计(4)——使用ActionBar+Fragment实现tab切换
  2. java集合学生信息_java 使用 集合 制作学生管理系统
  3. Aurora公式编辑器在64位Word 2013不显示选项卡
  4. Oracle 联机重做日志文件(ONLINE LOG FILE)
  5. python搭建django
  6. matplotlib包的学习(一)
  7. linux 信号量锁 内核,Linux内核中锁机制之信号量、读写信号量
  8. 晶闸管的原理及伏安特性
  9. 鸿蒙天钟壁纸,鸿蒙2.0桌面小工具时钟,日历显示不出来
  10. 新编c语言程序设计案例教程 pdf下载,新编C语言程序设计教程本科第章.pdf
  11. 钽电容器为什么被要求降额到额定值的1/3使用
  12. 乘法鉴相器的matlab仿真,基于COSTAS环的载波恢复技术
  13. 超参数调优方法整理大全
  14. google离线地图制作
  15. 关于“预习”的调查与思考( 云中逸客 )
  16. 【已解决】surface 电池不好充电显示“未连接”,将充电的接口换个方向就解决了
  17. 一个算法工程师复现算法的踩坑总结
  18. 如何对磁盘分区进行写保护
  19. python-docx文档高亮显示
  20. Spatial Attention

热门文章

  1. 为女儿示范的两张石膏像素描
  2. UART协议驱动设计
  3. Touch Driver介绍
  4. 64位Ubuntu kylin 16.04搭建tftp服务器
  5. 主管护士需要考计算机和英语吗,2020主管护师改为机考,一定要注意这些问题!...
  6. mysql对所有列的数据进行修改6_MySQL的SQL语句 - 数据定义语句(6)- ALTER TABLE 语句 (3)...
  7. 树莓派python开发工具哪个好_Thonny——树莓派上Python的最新IDE
  8. python自动化办公知识点整理汇总_python自动化办公小结
  9. 电子计算机技能竞赛数据,2015年浙江省中等职业学校计算机应用技术专业学生职业技能大赛“计算机检测维修与数据恢复”赛项规程.doc...
  10. applicationproperties不是小叶子_三角梅整株叶子发黄从这里找原因,早解决早生长!...