宏在C语言中经常使用,在linux的源码中可以看到很多宏的高级应用。因此不理解宏,就很难理解代码。本文参考一些互联网资料做一些总结,希望给大家带来帮助。

先说说使用宏的优点及缺点:

优点:

1.提高代码的可维护性:使用宏定义常量,在改常量值需要修改的时候,只需要改常量定义的地方即可。

2.提高代码的可读性:代码中有意义的名称比魔数跟具有可读性也更好理解。

缺点:

1.难以扩展:当宏变得复杂的时候,修改起来非常困难。

2.难以调试:很多调试工具,都难以跟踪到宏的内部代码。

宏的一些特点:

1.宏从文件头到文件位展开。

2.宏可以被定义,取消定义,重新定义。

3.宏与其他代码不同,是会被编译的。

4.宏仅仅在使用到它的地方才会展开。

5.宏可以用’\’连接成多行。

6.在编译的时候可以加上-E选项查看宏展开之后的代码。

宏的基本使用

宏定义

C语言中使用#define 关键字定义宏。宏可以定义为一个数值,或者一个代码片段。当宏定义为一个一个数值的时候,代码中使用宏名称的地方都会被宏的实际数值替换。宏也可以接受类型无关的参数,宏参数将会在宏展开的时候被实际的参数替换。下面是一个简单的代码,代码中定义了一个数值宏以及接受一个参数的宏,在最后一行中取消定义一个宏。宏定义的时候在值部分加上括号是一个好习惯。

   1: #define ENABLE_MY_FEAUTRE

   2: #define MAX_ITERATIONS   (4)

   3: #define IS_POSITIVE( _x ) ( _x > 0 )

   4:  

   5: #undef ENABLE_MY_FEATURE

条件检查

使用#if与#ifdef关键字可以做一些条件检查以及逻辑判断。可以通过判断一个宏是否被定义使用比如 OR ADN 以及NOT 甚至 <=等来实现特定的代码逻辑,在宏结束的地方一定要加上#endif。在条件判断中也可以使用#elif 和#else。这里是一个例子:

   1: #ifdef ENABLE_MY_FEATURE

   2: /* Implement my feature */

   3: ...

   4: #endif

   5:  

   6: #if (MAX_ITERATIONS > 5) && defined(ENABLE_MY_FEATURE)

   7: /* Implement the better implementation */

   8: #else

   9: /* Do something else */

  10: #endif

宏的中级应用

do-while(0)的妙用

do-while(0)一般用在宏中有多条命令的时候避免意外条件错误,下面是一个例子,条件为真的时候,执行我们定义的一个宏。

   1: if( condition )

   2:     DO_SOMETHING_HERE(x);

   3: else

   4:     ...

下面是DO_SOMETHING_HERE宏以及宏展开的样子:

   1: #define DO_SOMETHING_HERE(_x) foo(_x); bar(_x);

   2:  

   3: if( condition )

   4:     foo(_x); bar(_x);;

   5: else

   6:     ...

这样就会导致编译错误,因为条件为真的时候,将调用foo,但是bar总是会执行,这样就导致if终止,else找不到匹配的if。

下面是do-while(0)版本以及宏展开的样子:

   1: #define DO_SOMETHING_HERE(_x) do{ foo(_x); bar(_x); }while(0) 

   2:  

   3: if( condition )

   4:     do{ foo(_x); bar(_x); } while(0);

   5: else

   6:     ...

定位功能

大多数编译器都通过内置的宏提供定位功能,这点在日志中尤为有效。下面是常用的宏,

__FUNCTION__:指示当前函数

__LINE__:指示当前行号

__FILE__:指示当前源文件的名称

字符串转换

宏一个提供了一个将任何代码文本转换为字符串的功能,只需要在需要转换为字符串的代码文本之前加上#。下面是常用的做法:

   1: #define STR(_x)   #_x

   2: #define XSTR(_x)  STR(_x)

字符串连接

宏另外一个常用功能就是字符串连接,使用##连接需要连接的字符串。如下面代码所示:

   1: #define MACRO_CONCAT( _x, _y )   _x##_y

   2: #define FUNC_PROTO( _handler ) int handler_func_##_handler( int );

   3:  

   4: MACRO_CONCAT( hello, dolly ) /* Results in hellodolly */

   5: FUNC_PROTO( integer ) /* Results in: "int handler_func_integer( int );" It's a function prototype declaration */

可变参数

宏同样支持可变参数,就像printf一样,参数个数可以是任意多个。下面的例子,源自pcd代码,展示了我们可以如何包装printf。

   1: extern bool_t verboseOutput;

   2:  

   3: #define PCD_PRINT_PREFIX                            "pcd: "

   4: #define PCD_PRINTF_STDOUT( _format, _args... )        \

   5:     do { if( verboseOutput ) fprintf( stdout, "%s"_format "%s", PCD_PRINT_PREFIX, ##_args, ".\n" ); } while( 0 )

返回值

宏也可以参与计算并“返回”一个值。这个返回和函数返回是不一样的。在下面的例子中,用宏判断一个数字是奇数还是偶数,宏的返回值是string,我们用这个值打印我们的判断结果。

   1: #include <stdio.h>

   2: #include <stdlib.h>

   3:  

   4: #define IS_EVEN_STR( _x ) \

   5:     ( _x & 1 ? "odd" : "even" )

   6:  

   7: int main( int argc, char *argv[] )

   8: {   

   9:     int val;

  10:  

  11:     if(argc<2)

  12:         return;

  13:  

  14:     /* Convert to integer */

  15:     val = atoi(argv[1]);

  16:  

  17:     /* Print our conclusion */

  18:     printf( "The number %d is %s\n", val, IS_EVEN_STR(val));

  19:  

  20:     return 0;

  21: }

下面是程序的输出

   1: $ ./even 45

   2: The number 45 is odd

   3: $ ./even 64

   4: The number 64 is even

宏的高级应用

断言

下面的几个宏在代码调试中是很有用的,在断言失败的情况下打印详细的错误信息,然后终止程序。

   1: /* Crash the process */

   2: #define __CRASH()    (*(char *)NULL)

   3:  

   4: /* Generate a textual message about the assertion */

   5: #define __BUG_REPORT( _cond, _format, _args ... ) \

   6:     fprintf( stderr, "%s:%d: Assertion error in function '%s' for condition '%s': " _format "\n", \

   7:     __FILE__, __LINE__, __FUNCTION__, # _cond, ##_args ) && fflush( NULL ) != (EOF-1)

   8:  

   9: /* Check a condition, and report and crash in case the condition is false */

  10: #define MY_ASSERT( _cond, _format, _args ... ) \

  11: do { if(!(_cond)) { __CRASH() = __BUG_REPORT( _cond, _format, ##_args ); } } while( 0 )

下面我们看看如何使用这个宏,我们断言必须传递3个或4个参数,否则打印错误信息并终止程序。

   1: #include <stdio.h>

   2: #include <stdlib.h>

   3:  

   4: #define MIN_PARAMS 3

   5: #define MAX_PARAMS 4

   6:  

   7: int main( int argc, char *argv[] )

   8: {

   9:     int params = argc - 1;

  10:     MY_ASSERT( params >= MIN_PARAMS && params <= MAX_PARAMS,

  11:         "Invalid parameters! must specify at least %d parameters, where %d specified", MIN_PARAMS, params );

  12:     return 0;

  13: }

下面是程序的输出:

   1: $ ./macro 1 2

   2: macro.c:21: Assertion error in function 'main' for condition 'params >= 3 && params <= 5': Invalid parameters! must specify at least 3 parameters, where 2 specified

   3: Segmentation fault

   4: $ ./macro 1 2 3

   5: $ ./macro 1 2 3 4

   6: $ ./macro 1 2 3 4 5

   7: macro.c:21: Assertion error in function 'main' for condition 'params >= 3 && params <= 4': Invalid parameters! must specify at least 3 parameters, where 5 specified

   8: Segmentation fault

代码生成

如果你觉得宏断言比较cool的话,那么代码生成就更加cooler了。虽然这样的宏的可读性以及可维护性比较差,但是另一方面它是很多事情自动化减少了手工错误,并且当事情变得有规律的时候,这样的宏变得很有用。如果你发现代码中有很多重复性工作,你可以试试使用这样的宏。

下面是一个例子,假如你有一个关键字列表。你想创建这个关键字列表的枚举,为每个枚举生成回调函数与一个标记,你同样想将每个关键字设计成字符串类型以用于其他函数。如果不实用宏,你不得不手工做这个重复性工作。

下面是关键字:RULE, START_COND, COMMAND, END_COND, END_COND_TIMEOUT, FAILURE_ACTION, ACTIVE, SCHED, DAEMON, USER, VERSION and INCLUDE。最后5个关键字不强制实现 - 这是一个连接到每个关键字的属性标志。

第一步是生成包含关键字的逻辑列表,在列表内我们将关键字与其他信息整合,在我们的例子中是强制标记的设置。内部宏使我们可以有选择性地提取每行提供的信息。内部宏我们还没有定义,在我们根据实现需要进行定制,由于宏还没有展开所以不会有编译错误。需要注意的是我们没有必要在每一行都是用全部信息。

   1: /* Keyword,        Mandatory */

   2: #define PCD_PARSER_KEYWORDS \

   3:     PCD_PARSER_KEYWORD( RULE,               1 )\

   4:     PCD_PARSER_KEYWORD( START_COND,         1 )\

   5:     PCD_PARSER_KEYWORD( COMMAND,            1 )\

   6:     PCD_PARSER_KEYWORD( END_COND,           1 )\

   7:     PCD_PARSER_KEYWORD( END_COND_TIMEOUT,   1 )\

   8:     PCD_PARSER_KEYWORD( FAILURE_ACTION,     1 )\

   9:     PCD_PARSER_KEYWORD( ACTIVE,             1 )\

  10:     PCD_PARSER_KEYWORD( SCHED,              0 )\

  11:     PCD_PARSER_KEYWORD( DAEMON,             0 )\

  12:     PCD_PARSER_KEYWORD( USER,               0 )\

  13:     PCD_PARSER_KEYWORD( VERSION,            0 )\

  14:     PCD_PARSER_KEYWORD( INCLUDE,            0 )

下面我们开始生成代码,首先在头文件中生成一个枚举。我们定义一个带两个参数的宏PCD_PARSER_KEYWORD,不过只有keyword参数有用。

   1: /***********************************************

   2:  * Keyword enumeration

   3:  ***********************************************/

   4: #define SET_KEYWORD_ENUM(x) \

   5:  PCD_PARSER_KEYWORD_##x

   6:  

   7: #define PCD_PARSER_KEYWORD( keyword, mandatory ) \

   8:  SET_KEYWORD_ENUM( keyword ),

   9:  

  10: typedef enum parserKeywords_e

  11: {

  12:     PCD_PARSER_KEYWORDS

  13:  

  14:     PCD_PARSER_KEYWORD_LAST

  15:  

  16: } parserKeywords_e;

  17:  

  18: #undef PCD_PARSER_KEYWORD

下面是预处理输出,这里为了美观,加了换行。

   1: typedef enum parserKeywords_e

   2: {

   3:     PCD_PARSER_KEYWORD_RULE,

   4:     PCD_PARSER_KEYWORD_START_COND,

   5:     PCD_PARSER_KEYWORD_COMMAND,

   6:     PCD_PARSER_KEYWORD_END_COND,

   7:     PCD_PARSER_KEYWORD_END_COND_TIMEOUT,

   8:     PCD_PARSER_KEYWORD_FAILURE_ACTION,

   9:     PCD_PARSER_KEYWORD_ACTIVE, P

  10:     CD_PARSER_KEYWORD_SCHED,

  11:     PCD_PARSER_KEYWORD_DAEMON,

  12:     PCD_PARSER_KEYWORD_USER,

  13:     PCD_PARSER_KEYWORD_VERSION,

  14:     PCD_PARSER_KEYWORD_INCLUDE,

  15:  

  16:     PCD_PARSER_KEYWORD_LAST

  17:  

  18: } parserKeywords_e;

下面生成回调函数,为了用一个关键字生成回调函数原型,我们给每个关键字加上前缀与后缀。例子中所有的函数都是静态的,返回类型都是int32_t.前缀是PCD_parser_handle_,所有参数都是char* line。

   1: /**************************************************

   2:  * Declarations for the keyword handlers.

   3:  **************************************************/

   4: #define SET_HANDLER_FUNC(x)   PCD_parser_handle_##x

   5: #define PCD_PARSER_KEYWORD( keyword, mandatory )\

   6:     static int32_t SET_HANDLER_FUNC( keyword ) ( char *line );

   7:  

   8: PCD_PARSER_KEYWORDS

   9:  

  10: #undef PCD_PARSER_KEYWORD

下面是输出,为了美观做了调整

   1: static int32_t PCD_parser_handle_RULE ( char *line ); static int32_t PCD_parser_handle_START_COND ( char *line );

   2: static int32_t PCD_parser_handle_COMMAND ( char *line ); static int32_t PCD_parser_handle_END_COND ( char *line );

   3: static int32_t PCD_parser_handle_END_COND_TIMEOUT ( char *line ); static int32_t PCD_parser_handle_FAILURE_ACTION ( char *line );

   4: static int32_t PCD_parser_handle_ACTIVE ( char *line ); static int32_t PCD_parser_handle_SCHED ( char *line );

   5: static int32_t PCD_parser_handle_DAEMON ( char *line ); static int32_t PCD_parser_handle_USER ( char *line );

   6: static int32_t PCD_parser_handle_VERSION ( char *line ); static int32_t PCD_parser_handle_INCLUDE ( char *line );

请注意毕竟宏的生成有限,你必须根据你的需要实现函数代码。然而如果你的函数也是有规律的话,同样可以使用他们。假如我们有一个包含关键字,回调函数指针,标记值,以及他信息的结构体。我们可以使用下面的代码生成结构体。

   1: typedef struct configKeywordHandler_t

   2: {

   3:     char      *name;

   4:     int32_t     (*handler)(char *line);

   5:  

   6:     /* set at run time. */

   7:     u_int32_t    parse_flag;

   8:  

   9:     /* indicate if this is a mandatory field. */

  10:     u_int32_t    mandatory_flag;

  11:  

  12: } configKeywordHandler_t;

  13:  

  14: /**************************************************************************

  15:  * Initialize keyword array

  16:  **************************************************************************/

  17: #define PCD_PARSER_KEYWORD( keyword, mandatory ) \

  18:  { XSTR( keyword ), SET_HANDLER_FUNC( keyword ), 0, mandatory },

  19:  

  20: configKeywordHandler_t keywordHandlersList[] =

  21: {

  22:     PCD_PARSER_KEYWORDS

  23:     { NULL,       NULL,          0, 0},

  24: };

  25:  

  26: #undef PCD_PARSER_KEYWORD

XSTR将关键字转换为字符串,SET_HANDLER_FUNC生成函数体。下面是输出:

   1: configKeywordHandler_t keywordHandlersList[] =

   2: {

   3:     { "RULE", PCD_parser_handle_RULE, 0, 1 },

   4:     { "START_COND", PCD_parser_handle_START_COND, 0, 1 },

   5:     { "COMMAND", PCD_parser_handle_COMMAND, 0, 1 },

   6:     { "END_COND", PCD_parser_handle_END_COND, 0, 1 },

   7:     { "END_COND_TIMEOUT", PCD_parser_handle_END_COND_TIMEOUT, 0, 1 },

   8:     { "FAILURE_ACTION", PCD_parser_handle_FAILURE_ACTION, 0, 1 },

   9:     { "ACTIVE", PCD_parser_handle_ACTIVE, 0, 1 },

  10:     { "SCHED", PCD_parser_handle_SCHED, 0, 0 },

  11:     { "DAEMON", PCD_parser_handle_DAEMON, 0, 0 },

  12:     { "USER", PCD_parser_handle_USER, 0, 0 },

  13:     { "VERSION", PCD_parser_handle_VERSION, 0, 0 },

  14:     { "INCLUDE", PCD_parser_handle_INCLUDE, 0, 0 },

  15:     { ((void *)0), ((void *)0), 0, 0},

  16: };

总结:

一旦我们掌握了宏的核心思想以及如何根据需要使用它们,我们就会极大的提高工作效率。

C语言基础--宏的更多相关文章

  1. 注解是建立在class文件基础上的东西,同C语言的宏有异曲同工的效果

    注解是建立在class文件基础上的东西,同C语言的宏有异曲同工的效果 https://www.cnblogs.com/deman/p/5519901.html @是java注解,即annotation ...

  2. C语言基础回顾

    第一章 C语言基础 1.  C语言编译过程 预处理:宏替换.条件编译.头文件包含.特殊符号 编译.优化:翻译并优化成等价的中间代码表示或汇编代码 汇编:生成目标文件,及与源程序等效的目标的机器语言代码 ...

  3. 01_C语言基础

    内容提要: 1. C语言概述2. 数据类型.运算符与表达式3. C语言程序结构 4. VC6.0使用练习 知识详解01:C语言的历史 1. C语言与其它语言比较 汇编语言: (1).可直接对硬件进行操 ...

  4. C语言基础复习总结

    C语言基础复习总结 大一学的C++,不过后来一直没用,大多还给老师了,最近看传智李明杰老师的ios课程的C语言入门部分,用了一周,每晚上看大概两小时左右,效果真是顶一学期的课,也许是因为有开发经验吧, ...

  5. (cljs/run-at (->JSVM :browser) "语言基础")

    前言  两年多前知道cljs的存在时十分兴奋,但因为工作中根本用不上,国内也没有专门的职位于是搁置了对其的探索.而近一两年来又刮起了函数式编程的风潮,恰逢有幸主理新项目的前端架构,于是引入Ramda. ...

  6. C++学习1-(C语言基础、VS快捷键)

    C语言基础复习 1.三码 正数: 3码合1 ,正数的反码/补码就是其本身 负数: 原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值 原码:11010101 负数的反码是在其原码的基础上 ...

  7. D10——C语言基础学PYTHON

    C语言基础学习PYTHON——基础学习D10 20180906内容纲要: 1.协程 (1)yield (2)greenlet (3)gevent (4)gevent实现单线程下socket多并发 2. ...

  8. c语言基础知识要点

    C语言程序的构成 与C++.Java相比,C语言其实很简单,但却非常重要.因为它是C++.Java的基础.不把C语言基础打扎实,很难成为程序员高手. 一.C语言的结构 先通过一个简单的例子,把C语言的 ...

  9. NDK以及C语言基础语法(一)

    一.什么是NDK? Native Development Kit (本地开发工具包): NDK中提供了一系列的工具,帮助我们快速开发C/C++的动态库,并能自动将so文件和java文件一起打包成apk ...

随机推荐

  1. ExtJS4.2学习(11)可拖放的表格(转)

    鸣谢:http://www.shuyangyang.com.cn/jishuliangongfang/qianduanjishu/2013-11-18/180.html --------------- ...

  2. [转载]jquery cookie的用法

    原文地址:http://www.cnblogs.com/qiantuwuliang/archive/2009/07/19/1526663.html jQuery cookie是个很好的cookie插件 ...

  3. fork与vfork的区别

    fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别: 1.  fork  ():子进程拷贝父进程的数据段,代码段     vfork ( ):子进程与父进程共享数据段 ...

  4. hdu 1847 Good Luck in CET-4 Everybody! 博弈论

    方法一:找规律,很容易知道 #include<stdio.h> int main(){ int n; while(scanf("%d",&n)!=EOF){ p ...

  5. [itint5]二叉树转换线索二叉树

    http://www.itint5.com/oj/#27 用了基于stack的中序遍历,记录一下last,就很简单了. #include <stack> /*树结点的定义(请不要在代码中定 ...

  6. nginx配置负载均衡与反向代理

    #给文件夹授权   1 chown -R www:www /usr/local/nginx #修改配置文件vim nginx.conf   1 2 3 4 5 6 7 8 9 10 11 12 13 ...

  7. 网上图书商城项目学习笔记-014购物车模块页面javascrip

    一.流程分析 二.代码 1.view层 (1)list.jsp <%@ page language="java" import="java.util.*" ...

  8. C++动态链接库测试实例

    前话 上一章节我导出了一个动态链接库 要使用该链接库,我们还需要该链接库对外公开的函数,即头文件 下面开始实例 测试实例 第一步--将动态链接库的dll.lib.和头文件导入项目中 文件目录如下: 项 ...

  9. P137、面试题23:从上往下打印二叉树

    题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印.例如输入如图的二叉树,则依次打印出8,6,10,5,7,9,11.(其实是按层遍历)二叉树结点的定义如下:struct Bin ...

  10. Android安装常见问题

    在初次运行Android程序的时候会出现类似的错误,导致程序无法继续运行,如下面的几个例子: 问题1:PC安卓模拟器 PANIC: Could not open: C:\Documents and S ...