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

总结:

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

上一篇:开源项目Telegram源码 Telegram for Android Source


下一篇:Android进阶笔记20:Android手机屏幕坐标系