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

总结:

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

原文地址:https://www.cnblogs.com/xiaofeifei/p/3327045.html