宏在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: };
总结:
一旦我们掌握了宏的核心思想以及如何根据需要使用它们,我们就会极大的提高工作效率。