前言
在C语言的编程中,宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的替换。在实践中阅读他人的代码和自己编写代码中,如果可以灵活的使用的宏定义可以是提高代码的可读性,对于代码的可读性我个人觉得也是代码的优雅的一种的指标,有时候为了可读性甚至可以牺牲一部分的效率,毕竟代码可读性提高了对于代码的协同开发和后续的维护大有益处。下面就总结工作中一些常用用到的宏定义:
常用的宏定义
求解数组长度的宏
这个宏在许多代码常常被使用
#define ARRAY_LENGTH(_array) (int)(sizeof(_array)/sizeof(_array[0]))
我们不妨对比一下不使用宏定义的代码和使用宏定义的代码
不使用宏定义进行数据的遍历
for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++){
...
}
使用宏定义的代码
for(int i = 0; i < ARRAY_LENGTH(array); i++){
...
}
两者进行对比后显然使用宏定义的代码的可读性更好。
而且如果你细心一点你会发现在宏定义后使用了int进行强制转换,很多的代码中并没用进行强制的转换,sizeof关键词返回是一个无符号的数字,这个没有任务问题,因为数组的长度必然是大于0的。但是在使用中存在这样一个隐患:
if(-1 > sizeof(array)/sizeof(array[0]){
...
}
如果你使用负数和sizeof(array)/sizeof(array[0]进行对比,-1会自动强制转换为无符号的类型,这样导致条件满足。然后我们想要的是条件不满足,所以对求数组长度的宏进行强制转换(int)是一种避免方法。
求取两数的最大值和最小值
#define MAX(x,y) (((x)>(y))?(x):(y))
#define MIN(x,y) (((x)<(y))?(x):(y))
得到一个field在结构体当中的偏移量
#define FPOS(type, field) ((size_t)&(((type *)0)->field))
这个代码也不难理解,解析过程如下:
- (type *)0 --- 先把0地址强转为结构体类型的指针
- ((type *)0)->field) --- 在结构体中找到成员field的偏移地址
- 返回偏移地址,由于开始是0地址,所以成员相对结构体的偏移地址 == 成员的地址
得到结构体中field的字节数
#define FSIZ( type, field ) sizeof( ((type *) 0)->field )
连接两个参数
在宏定义中##表示连接两个参数,#用于替换参数,根据这条规则,所以你需要连接两个参数可以使用下面宏
#define __CONCAT(a, b) a ## b
编译时检查错误
如果希望在编译期可以检查代码的某些条件是否成立,如果条件成立,代码出现错误无法编译,反之可以通过编译,可以使用下面这个宏来进行实现。
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
!!(e) 对 e 的结果进行两次求非. 如果e为0,则结果为0; 如果 e 不为 0, 则结果为1。所以上述表达式的结果有两种:
- condition为真时,sizeof(char[-1]),产生错误,编译不通过
- condition为假时,sizeof(char[1]),编译通过
整除y并且大于x,最接近x的值,向上取整
返回一个能够整除y并且大于x,最接近x的值,向上取整,可用于地址的内存对齐:
#define roundup(x, y) ( \
{ \
const typeof(y) __y = y; \
(((x) + (__y - 1)) / __y) * __y; \
} \
)
判断val是否在lo和hi的范围内
/**
* clamp - return a value clamped to a given range with strict typechecking
* @val: current value
* @lo: lowest allowable value
* @hi: highest allowable value
*
* This macro does strict typechecking of @lo/@hi to make sure they are of the
* same type as @val. See the unnecessary pointer comparisons.
*/
#define clamp(val, lo, hi) min((typeof(val))max(val, lo), hi)
判断val是否在lo和hi的范围内,如果小于lo,返回lo,如果大于hi则返回hi,如果在lo和hi之间就返回val .简单可以理解为取三数之中的中间值。如果对这个宏稍微做一点加工就实现一个比较常用的功能。
#define clamp(val, lo, hi) min((typeof(val))max(val, lo), hi)
#define is_in_area(val, lo, hi) (clamp(val, lo, hi) == val)
这个宏可以判断val是否在(lo,hi)的区间,对一些对于不同的区间进行不同的处理的过程是可以提高可读性和精简代码。
交换两个数
#define swap(a, b) do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)
这个是一个交换两个数的宏,实用typeof获取要交换的数据的变量类型,然后使用一个temp进行数据的交换,这个方法对于任何学习C语言的学生来说都是一道入门题目。
但是在linux内核中,有这样一个宏也可以实现相同的功能,宏定义如下。
#define swap(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b)))
这个宏看起来很疑惑,想来许多第一次看到这个宏定义的使用往往会很疑惑其原理。首先要明白了是,^是按位异或表示,即将a与b的对应位进行异或运算,同为0或者同为1时,对应位结果为0;否则为1。知道这一点分析一下这个定义。
- (a) ^= (b) 根据我二进制将a和b不同位取出来置为1 比如0010 ^=1111 = 1101 所以这个式子可以理解就是取出a和b的差异位存储在a中。
- (b) ^= (a) b和(a和b)之间的差异位异或会将b赋值为原本的a 比如1111^1101 = 0010
- (a) ^= (b) 会将a赋值为原本的b
这个代码看起很神奇,但是要注意的一点就是宏定义只能实现交换整形变量的数字,对于浮点型变量是不能实现交换的功能的。
字节交换宏
#define BSWAP_8(x) ((x) & 0xff)
#define BSWAP_16(x) ((BSWAP_8(x) << 8) | BSWAP_8((x) >> 8))
#define BSWAP_32(x) ((BSWAP_16(x) << 16) | BSWAP_16((x) >> 16))
#define BSWAP_64(x) ((BSWAP_32(x) << 32) | BSWAP_32((x) >> 32))
这个宏的定义是实现字节的颠倒,在一些协议和移植的时候,需要交换数据大小端是十分优雅的(好用的)
类型检查宏
#define typecheck(type,x) \
({ type __dummy; \
typeof(x) __dummy2; \
(void)(&__dummy == &__dummy2); \
1; \
})
这个宏在linux内核常常被使用到,用于判断变量的类型和我们期望的数据类型是否是一致,这个语句的重要一点就是(void)(&__dummy == &__dummy2); 这个句话,在编译时利用两个指针的相等性比较进行检测。GUN C 指出不同类型指针的比较在编译时会输出一条warning:
warning: comparison of distinct pointer types lacks a cast
最后比较结果转换为void,即丢弃这个结果。
判断浮点型是否成于零
浮点在和零进行比较并无法直接使用==进行比较,一般是使用abs(x-0.0f)>一个误差来判断浮点型是否等于0,但在不同产品和系统中,这个误差可能选择不一样,对于高精度的这个误差需要很小,对于不是要求精度很高的产品中,这个误差可能比较大。
#define EPS 1e-6 // 定义浮点型的判零边界
#define IsZero(x) (fabs(x)<=EPS)// 判断浮点数是否等于零
标签:常用,定义,val,lo,工作,hi,sizeof,define
From: https://www.cnblogs.com/Kroner/p/17004603.html