• C语言基础宏


    宏在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: };

    总结:

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

  • 相关阅读:
    eclipse下切换svn用户
    Netty实现服务端客户端长连接通讯及心跳检测
    Spring Batch系列总括(转载)
    SQL中的Null深入研究分析
    MySQL报错“1366
    Memcache学习php完整一例
    Memcache学习笔记
    递归和迭代区别
    解决textarea 输出有空格问题
    解决mysql安装出现error Nr.1045问题
  • 原文地址:https://www.cnblogs.com/xiaofeifei/p/3327045.html
Copyright © 2020-2023  润新知