C语言宏定义技巧(常用宏定义)
以下内容来自网络
漂浮不定 整理
写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性 等等。下面列举一些成熟软件中常用得宏定义。。。。。。
1,防止一个头文件被重复包含

#ifndef COMDEF_H
#define COMDEF_H
//头文件内容
#endif
2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。
typedef unsigned char boolean; /* Boolean value type. */
typedef unsigned long int uint32; /* Unsigned 32 bit value */
typedef unsigned short uint16; /* Unsigned 16 bit value */
typedef unsigned char uint8; /* Unsigned 8 bit value */
typedef signed long int int32; /* Signed 32 bit value */
typedef signed short int16; /* Signed 16 bit value */
typedef signed char int8; /* Signed 8 bit value */
typedef unsigned char byte; /* Unsigned 8 bit value type. */ typedef unsigned short word; /* Unsinged 16 bit value type. */ typedef unsigned long dword; /* Unsigned 32 bit value type. */
//下面的不建议使用
typedef unsigned char uint1; /* Unsigned 8 bit value type. */ typedef unsigned short uint2; /* Unsigned 16 bit value type. */ typedef unsigned long uint4; /* Unsigned 32 bit value type. */ typedef signed char int1; /* Signed 8 bit value type. */ typedef signed short int2; /* Signed 16 bit value type. */ typedef long int int4; /* Signed 32 bit value type. */ typedef signed long sint31; /* Signed 32 bit value */
typedef signed short sint15; /* Signed 16 bit value */ typedef signed char sint7; /* Signed 8 bit value */
3,得到指定地址上的一个字节或字
#define MEM_B( x ) ( *( (byte *) (x) ) )
#define MEM_W( x ) ( *( (word *) (x) ) )
4,求最大值和最小值
#define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )
#define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )
5,得到一个field在结构体(struct)中的偏移量
#define FPOS( type, field ) /*lint -e545 */
( (dword) &(( type *) 0)-> field ) /*lint +e545 */
6,得到一个结构体中field所占用的字节数
#define FSIZ( type, field ) sizeof( ((type *) 0)->field )
7,按照LSB格式把两个字节转化为一个Word
#define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )
8,按照LSB格式把一个Word转化为两个字节
#define FLOPW( ray, val )
(ray)[0] = ((val) / 256);
(ray)[1] = ((val) & 0xFF)
9,得到一个变量的地址(word宽度)
#define B_PTR( var ) ( (byte *) (void *) &(var) )
#define W_PTR( var ) ( (word *) (void *) &(var) )
10,得到一个字的高位和低位字节
#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
11,返回一个比X大的最接近的8的倍数
#define RND8( x ) ((((x) + 7) / 8 ) * 8 )
12,将一个字母转换为大写
#define UPCASE( c ) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (c) ) 13,判断字符是不是10进值的数字
#define DECCHK( c ) ((c) >= '0' && (c) <= '9')
14,判断字符是不是16进值的数字
#define HEXCHK( c ) ( ((c) >= '0' && (c) <= '9') ||
((c) >= 'A' && (c) <= 'F') ||
((c) >= 'a' && (c) <= 'f') )
15,防止溢出的一个方法
#define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val)) 16,返回数组元素的个数
#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
17,返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
#define MOD_BY_POWER_OF_TWO( val, mod_by )
( (dword)(val) & (dword)((mod_by)-1) )
18,对于IO空间映射在存储空间的结构,输入输出处理
#define inp(port) (*((volatile byte *) (port)))
#define inpw(port) (*((volatile word *) (port)))
#define inpdw(port) (*((volatile dword *)(port)))
#define outp(port, val) (*((volatile byte *) (port)) = ((byte) (val))) #define outpw(port, val) (*((volatile word *) (port)) = ((word) (val))) #define outpdw(port, val)(*((volatile dword *) (port)) = ((dword) (val))) 19,使用一些宏跟踪调试
尺度预界说宏(Standard Predefined Macros)
__FILE__ 当前源文件,形如"test.c"
__LINE__ 当前行数,形如"229"
__DATE__ 编译日期,形如"Feb 12 1996"
__TIME__ 编译时候,形如"23:59:01"
应用实例(摘自gcc手册)
//下面的代码能打印出报错的具体位置(源文件和代码行)
fprintf (stderr, "Internal error: "
"negative string length "
"%s at %s, line %s.",
length, __FILE__, __LINE__);
20,宏定义防止使用是错误
用小括号包含。
例如:#define ADD(a,b) (a+b)
用do{}while(0)语句包含多语句防止错误
例如:#difne do(a,b) a+b;
a++;
应用时:if(….)
do(a,b); //产生错误
else
解决方法: #difne do(a,b) do{a+b;
a++;}while(0)
宏中"#"和"##"的用法
一、一般用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起. 用法:
#include<cstdio>
#include<climits>
using namespace std;
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
int main()
{
printf(STR(vck)); // 输出字符串"vck"
printf("%dn", CONS(2,3)); // 2e3 输出:2000
return 0;
}
二、当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开.
1, 非'#'和'##'的情况
#define TOW (2)
#define MUL(a,b) (a*b)
printf("%d*%d=%dn", TOW, TOW, MUL(TOW,TOW));
这行的宏会被展开为:
printf("%d*%d=%dn", (2), (2), ((2)*(2)));
MUL里的参数TOW会被展开为(2).
2, 当有'#'或'##'的时候
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf("int max: %sn", STR(INT_MAX)); // INT_MAX #include<climits> 这行会被展开为:
printf("int max: %sn", "INT_MAX");
printf("%sn", CONS(A, A)); // compile error
这一行则是:
printf("%sn", int(AeA));
INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏. 加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.
#define A (2)
#define _STR(s) #s
#define STR(s) _STR(s) // 转换宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a,b) _CONS(a,b) // 转换宏
printf("int max: %sn", STR(INT_MAX));// INT_MAX,int型的最大值,为一个变量 #include<climits>
输出为: int max: 0x7fffffff
STR(INT_MAX) --> _STR(0x7fffffff) 然后再转换成字符串;
printf("%dn", CONS(A, A));
输出为:200
CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))
三、'#'和'##'的一些应用特例
1、合并匿名变量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line) #define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示该行行号; 第一层:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);
第二层: --> ___ANONYMOUS1(static int, _anonymous, 70); 第三层: --> static int _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;
2、填充结构
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相当于:
MSG _msg[] = {{OPEN, "OPEN"},
{CLOSE, "CLOSE"}};
3、记录文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
4、得到一个数值类型所对应的字符串缓冲大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
--> char buf[sizeof "0x7fffffff"];
这里相当于:
char buf[11];
C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。下面对常遇到的宏的使用问题做了简单总结。
关于#和##在C语言的宏中,
#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量 通过替换后在其左右各加上一个双引号。比如下面代码中的宏:
#define WARN_IF(EXP) do{ if (EXP) fprintf(stderr, "Warning: " #EXP "n"); } while(0)
那么实际使用中会出现下面所示的替换过程:
WARN_IF (divider == 0);
被替换为
do {
if (divider == 0)
fprintf(stderr, "Warning" "divider == 0" "n");
} while(0);
这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。
##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定 是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:
struct command
{
char * name;
void (*function) (void);
};
#define COMMAND(NAME) { NAME, NAME ## _command }
// 然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了: struct command commands[] = {
COMMAND(quit),
COMMAND(help),
...
}
COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。我们还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的。比如:
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary); // 这里这个语句将展开为:
// typedef struct _record_type name_company_position_salary;
关于...的使用
...在C宏中称为Variadic Macro,也就是变参宏。比如:
#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)
// 或者
#define myprintf(templt,args...) fprintf(stderr,templt,args)
第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:
myprintf(templt,);
的形式。这时的替换过程为:
myprintf("Error!n",);
替换为:
fprintf(stderr,"Error!n",);
这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:
myprintf(templt);
而它将会被通过替换变成:
fprintf(stderr,"Error!n",);
很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:
myprintf(templt);
被转化为:
fprintf(stderr,templt);
这样如果templt合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。
错误的嵌套-Misnesting
宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。
由操作符优先级引起的问题-Operator Precedence Problem
由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:
#define ceil_div(x, y) (x + y - 1) / y
那么
a = ceil_div( b & c, sizeof(int) );
将被转化为:
a = ( b & c + sizeof(int) - 1) / sizeof(int);
// 由于+/-的优先级高于&的优先级,那么上面式子等同于:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:
#define ceil_div(x, y) (((x) + (y) - 1) / (y))
消除多余的分号-Semicolon Swallowing
通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:
MY_MACRO(x);
但是如果是下面的情况:
#define MY_MACRO(x) { /* line 1 */ /* line 2 */ /* line 3 */ }
//...
if (condition())
MY_MACRO(a);
else
{...}
这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:
#define MY_MACRO(x) do {
/* line 1 */ /* line 2 */ /* line 3 */ } while(0)
这样只要保证总是使用分号,就不会有任何问题。
Duplication of Side Effects
这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:
#define min(X,Y) ((X) > (Y) ? (Y) : (X))
//...
c = min(a,foo(b));
这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:
#define min(X,Y) ({ typeof (X) x_ = (X); typeof (Y) y_ = (Y); (x_ < y_) ? x_ : y_; }) ({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。
assert与嵌入式c语言调试开关
在调试程序时,经常会用到assert和printf之类的函数,我最近做的这个工程里就有几百个assert,在你自认为程序已经没有bug的时候,就要除去这些调试代码,应为系统在正常运行时这些用于调试的信息是无用的,而且会占用时间和空间。怎么删除呢,俺以前都是用笨方法,一个一个注释,能用注释也是经过改进的方法,俺最早都是删掉之后出了问题再重新写的,但是这次几百个一个一个删除的话可是要了俺的小命了,一首mp3听完,还不到一百个。以前看过st的函数库,老外的代码就是规范,俺现在的代码好多都是在st和ti那里照搬的,呵呵。
下面给出最简单的一种方法:
#define DEBUG
#ifdef DEBUG
#define PRINTF(x) printf x
#else
#define PRINTF(x) ((void)0)
#endif
使用时,PRINTF(( "Hello World!nr" ));
注意这里是两个括号,一个会报错的
不使用时,直接将"#define DEBUG"屏蔽掉
另外一个调试时常用的方法是assert,还是在一个头文件里,这里用的是STM32函数库的例子
#ifdef DEBUG 1
/*******************************************************************************
* Macro Name : assert_param
* Description : The assert_param macro is used for function's parameters check. * It is used only if the library is compiled in DEBUG mode.
* Input : - expr: If expr is false, it calls assert_failed function
* which reports the name of the source file and the source
* line number of the call that failed.
* If expr is true, it returns no value.
* Return : None
*******************************************************************************/
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((u8 *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */ void assert_failed(u8* file, u32 line);
#else
#define assert_param(expr) ((void)0)
#endif/* DEBUG */
//assert_failed此函数要自己定义
#ifdef DEBUG
/*******************************************************************************
* Function Name : assert_failed
* Description : Reports the name of the source file and the source line number * where the assert_param error has occurred.
* Input : - file: pointer to the source file name
* - line: assert_param error line source number
* Output : None
* Return : None
*******************************************************************************/
void assert_failed(u8* file, u32 line)
{
/* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %drn", file, line) */ /* Infinite loop */
while (1){
}
}
#endif
调试程序尤其是数据结构函数调用经常进行断言是一种好的习惯。本文简单介绍一下断言的使用方法,很简单Come on~
assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:
#include <assert.h>
void assert( int expression );
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
请看下面的程序清单badptr.c:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
int main( void )
{
FILE *fp;
fp = fopen( "test.txt", "w" );//以可写的方式打开一个文件,如果不存在就创建一个同名文件
assert( fp ); //所以这里不会出错
fclose( fp );
fp = fopen( "noexitfile.txt", "r" );//以只读的方式打开一个文件,如果不存在就打开文件失败
assert( fp ); //所以这里出错
fclose( fp ); //程序永远都执行不到这里来 return 0;
}
[root@localhost error_process]# gcc badptr.c
[root@localhost error_process]# ./a.out
a.out: badptr.c:14: main: Assertion `fp' failed.
已放弃
使用assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
在调试结束后,可以通过在包含#include <assert.h>的语句之前插入 #define NDEBUG 来禁用assert调用,示例代码如下:
#include <stdio.h>
#define NDEBUG
#include <assert.h>
用法总结与注意事项:
1)在函数开始处检验传入参数的合法性
如:
int resetBufferSize(int nNewSize)
{
//功能:改变缓冲区大小,
//参数:nNewSize 缓冲区新长度
//返回值:缓冲区当前长度
//说明:保持原信息内容不变 nNewSize<=0表示清除缓冲区
assert(nNewSize >= 0);
assert(nNewSize <= MAX_BUFFER_SIZE);
...
}
2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
不好: assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize);
好: assert(nOffset >= 0);
assert(nOffset+nSize <= m_nInfomationSize);
3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题
错误: assert(i++ < 100)
这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。
正确: assert(i < 100)i++;
4)assert和后面的语句应空一行,以形成逻辑和视觉上的一致感
5)有的地方,assert不能代替条件过滤
assert和if的区别:首先,这两个处于c语言中的不同等级,assert不过是系统提供的一个函数,而if则是关键字。其次,两个的语法也不一样,你可以写if(1){}但是写个assert(1){}却不对,当然,这个区别是第一点造成的在语义上,if就不用解释了吧。
assert的语义如下:在debug编译模式下,如果assert的表达式求值为false,就会中断程序;在release模式下,则没有任何操作。因此可以说,assert就是在调试模式下用来确保制定条件被满足的一种手法,比如说,你想确保a>0,你就可以写assert(a>0);如果在运行过程中,a<=0了,代码就会中断,利用调试器很容易发现问题所在。
在“应用程序调试"一书里,作者强烈推荐使用assert,他的代码让同事都抱怨assert太多了。。。。。。
assert()
C语言的assert(),中文翻译为断言,一旦触发,带调试信息的程序就会立刻退出并且给出错误信息,这一点是俺这样的IT民工都知道的,因此很多人对assert()是又爱又恨,爱她因为她能时不时给出点错误信息,恨她因为不去掉的话老是在你不希望她出现的时候蹦出来。
其实,我觉得对于程序而言,assert()的做法是一种勇敢的人生态度,面对不可逆转的错误,究竟是一错再错的执行下去,还是拿出一点舍身取义的勇气,终止自己继续执行的权力?assert()选择了后者。
但有些情况下不应该使用assert(),而应该利用其他方式来检测。一切都得视情况而定,例如某些函数的返回值检查:
ptr = malloc(1024);
assert(ptr);
采用assert()来做这样的判断是不明智的,但如果在一个接口定义良好的函数内使用assert()却可以保证程序能够正确的履行接口所定义的任务。例如:
void enter_without_lock(struct something_t * foo)
{
assert(NOT_HELD_MUTEX(foo->mutex));
........
}
因此如果你还在写程序,尤其是需要建立一个复杂的framework时,拿出点勇气来,多放几个assert()到这样的关键位置,不要轻易的去掉它,调试调试再调试,直到所有的assert()都不能正常通过,再发布你的release版本。
下面总结和宏和函数的不同之处,以供大家写代码时使用,这段总结摘自《C和指针》一书。
属性 #define宏 函数
代码长度 每次使用时,宏代码都被插入到程函数代码只出现于一个地方:每次使用这
序中。除了非常小的宏之外,程序个函数时,都调用那个地方的同一份代码
的长度将大幅度增长
执行速度 更快 存在函数调用、返回的额外开销 操作符优先级 宏参数的求值是在所有周围表达式函数参数只在函数调用时求值一次,它的
的上下文环境里,除非它们加上括结果值传递给函数。表达式的求值结果更
号,否则邻近操作符的优先级可能
产生不可预料的结果。
参数求值 参数用于宏定义时,每次都将重新参数在函数调用前只求值一次,在函数中
求值,由于多次求值,具有副作用多次使用参数并不会导致多次求值过程,
的参数可能会产生不可预测的结参数的副作用并不会造成任何特殊问题。
果。
参数类型 宏与类型无关,只要参数的操作是函数的参数是与类型有关系的,如果参数
合法的,它可以用于任何参数类型。 的类型不同,就需要使用不同的函数,即
使它们执行的任务是相同的。
容易预测。
百度搜索“爱华网”,专业资料,生活学习,尽在爱华网