common.h
#ifndef __COMMON_H_
#define __COMMON_H_
/**
* int64_t数的输出:%lld ;
* uint64_t数的输出:%llu ;
* uint64_t数十六进制输出:%llx ;
*/
#include <stdint.h> /* 引入uintx_t类型*/
#include <stdbool.h> /* 引入bool(true/false)类型*/
// 若x、y不是同一种数据类型,编译时会报warning!
#define MIN(x, y) ({ \
typeof(x) _min1__ = (x); \
typeof(y) _min2__ = (y); \
(void) (&_min1__ == &_min2__); \
_min1__ < _min2__ ? _min1__ : _min2__; })
#define MAX(x, y) ({ \
typeof(x) _max1__ = (x); \
typeof(y) _max2__ = (y); \
(void) (&_max1__ == &_max2__); \
_max1__ > _max2__ ? _max1__ : _max2__; })
#define MIN3(x, y, z) MIN((typeof(x))MIN(x, y), z)
#define MAX3(x, y, z) MAX((typeof(x))MAX(x, y), z)
/*
* ..and if you can't take the strict
* types, you can specify one yourself.
*
* Or not use min/max/clamp at all, of course.
*/
#define MIN_T(type, x, y) ({ \
type _min1__ = (x); \
type _min2__ = (y); \
_min1__ < _min2__ ? _min1__ : _min2__; })
#define MAX_T(type, x, y) ({ \
type _max1__ = (x); \
type _max2__ = (y); \
_max1__ > _max2__ ? _max1__ : _max2__; })
/**
* swap - swap values of @a and @b
* @a: first value
* @b: second value
*/
#define SWAP(a, b) \
do { typeof(a) _tmp__ = (a); (a) = (b); (b) = _tmp__; } while (0)
/**
* ARRAY_SIZE - get the number of elements in array @arr
* @arr: array to be sized
*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
/* 判断x是否是以a对齐的*/
//#define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0)
#define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0 ? true : false)
/*
* abs() handles unsigned and signed longs, ints, shorts and chars. For all
* input types abs() returns a signed long.
* abs() should not be used for 64-bit types (s64, u64, long long) - use abs64()
* for those.
*/
#define ABS(x) ({ \
long ret; \
if (sizeof(x) == sizeof(long)) { \
long _x__ = (x); \
ret = (_x__ < 0) ? -_x__ : _x__; \
} else { \
int _x__ = (x); \
ret = (_x__ < 0) ? -_x__ : _x__; \
} \
ret; \
})
/* 向free()传递一个NULL参数不会产生任何效果*/
#define SAFE_FREE(p) do{free(p); p=NULL;} while(0)
/**
* 这条语句的目的就是获取TYPE类型中MEMBER成员的相对偏移地址
* 在C标准库中(stddef.h)也可以找到offsetof;由于在内核空间中没有标准C库,因此就出现了下面的宏定义
*/
//#define OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/* 在64bit系统中,size_t为无符号的64bit整型*/
#define OFFSETOF(TYPE, MEMBER) ((uint64_t) &((TYPE *)0)->MEMBER)
/* 在32bit系统中,size_t为无符号的32bit整型*/
//#define OFFSETOF(TYPE, MEMBER) ((uint32_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - OFFSETOF(type,member) );})
#endif
GNU C 和 ANSI C
Linux上使用GNU C;GNU C对标准C进行了一系列的扩展,以增强标准C的功能。
- 语句表达式
GNU C把包含在括号中的复合语句看成是一个表达式,它可以出现在任何允许表达式的地方。
为GCC的一个扩展特性,形如({ ... })
这样的代码块会被视为一条语句,称作为 语句表达式。其计算结果是{ ... }
中最后一条语句的计算结果。 - typeof关键字
typeof(x)可以获得变量x的类型
内核标准实现
- 使用宏实现MIN/MAX函数
Linux内核实现(\linux-5.14.7\include\linux\minmax.h)
/*
* min()/max()/clamp() macros must accomplish three things:
*
* - avoid multiple evaluations of the arguments (so side-effects like
* "x++" happen only once) when non-constant.
* - perform strict type-checking (to generate warnings instead of
* nasty runtime surprises). See the "unnecessary" pointer comparison
* in __typecheck().
* - retain result as a constant expressions when called with only
* constant expressions (to avoid tripping VLA warnings in stack
* allocation usage).
*/
#define __typecheck(x, y) \
(!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
#define __no_side_effects(x, y) \
(__is_constexpr(x) && __is_constexpr(y))
#define __safe_cmp(x, y) \
(__typecheck(x, y) && __no_side_effects(x, y))
#define __cmp(x, y, op) ((x) op (y) ? (x) : (y))
#define __cmp_once(x, y, unique_x, unique_y, op) ({ \
typeof(x) unique_x = (x); \
typeof(y) unique_y = (y); \
__cmp(unique_x, unique_y, op); })
#define __careful_cmp(x, y, op) \
__builtin_choose_expr(__safe_cmp(x, y), \
__cmp(x, y, op), \
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
/**
* min - return minimum of two values of the same or compatible types
* @x: first value
* @y: second value
*/
#define min(x, y) __careful_cmp(x, y, <)
/**
* max - return maximum of two values of the same or compatible types
* @x: first value
* @y: second value
*/
#define max(x, y) __careful_cmp(x, y, >)
/**
* swap - swap values of @a and @b
* @a: first value
* @b: second value
*/
#define swap(a, b) \
do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)
最基本的形式:
#define MIN(a, b) ((a) < (b) ? (a) : (b))
存在的问题:
I、若a和b的类型不一样,理论上是无法比较的;上述实现未做类型判断
II、MIN(a++, b),若a<b,则a会自增2次
为解决上面存在的问题,改进版实现如下:
// 若x、y不是同一种数据类型,编译时会报warning!
#define MIN(x, y) ({ \
typeof(x) _min1__ = (x); \
typeof(y) _min2__ = (y); \
(void) (&_min1__ == &_min2__); \
_min1__ < _min2__ ? _min1__ : _min2__; })
上述宏定义展开后的结果为最后一行返回的结果。注意,这个扩展特性不是所有编译器都有的,如果用VS编译上述代码,是无法通过编译的。
这个宏定义中,先根据x
, y
的类型生成两个局部变量_min1__
和_min2__
,之后比较其大小,返回较小的一个,这样就保证了宏参数只会被执行一次,避免了上述副作用。另外,第4行代码其实是没有实际作用的,其意义在于:若_min1__
和_min2__
的类型不一致,比较其地址时编译器会给出一个Warning
,这样可以避免一些潜在的错误发生。
存在的问题:
I、当输入参数和宏定义内部使用的局部变量重名时,代码执行会不符合预期
输入参数和宏定义内部使用的局部变量重名,会导致在宏定义的语句块内,外层同名变量的作用域被内层局部变量的作用域所屏蔽。知道问题的原因后解决方法就很清晰了,只要避免重名就可以了,其实上述宏定义中使用_min1__
和_min2__
这样的名字也是为了避免重名,然而,靠起特殊的名字这种方法不是那么的优雅,故实际新版的Linux Kernel中使用了编译器产生的唯一名称来解决这一问题,就是最先描述的实现。
结论
输入参数和宏定义内部使用的局部变量(_min1__
和_min2__
)重名的情况基本上不会发生,所以使用如下实现即可有效达到我们的目的(\linux-4.0.6\include\linux\kernel.h既是采用的如下实现):
// 若x、y不是同一种数据类型,编译时会报warning!
#define MIN(x, y) ({ \
typeof(x) _min1__ = (x); \
typeof(y) _min2__ = (y); \
(void) (&_min1__ == &_min2__); \
_min1__ < _min2__ ? _min1__ : _min2__; })
测试代码:
#include <stdio.h>
#define MIN(x, y) ({ \
typeof(x) _min1__ = (x); \
typeof(y) _min2__ = (y); \
(void) (&_min1__ == &_min2__); \
_min1__ < _min2__ ? _min1__ : _min2__; })
void main(void)
{
int a = 10;
float b = 9.8;
printf("\t\t\t MIN=%u \n", MIN(a, b));
}
结果:
- 通过结构体成员获取结构体指针container_of
Linux内核实现(\linux-5.14.7\include\linux\kernel.h)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
结论
使用如下版本即可实现(\linux-4.0.6\include\linux\kernel.h 和 linux-4.0.6\include\linux\stddef.h):
/**
* 这条语句的目的就是获取TYPE类型中MEMBER成员的相对偏移地址;
* 在C标准库中(stddef.h)也可以找到offsetof;由于在内核空间中没有标准C库,因此就出现了下面的宏定义;
*/
#define OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - OFFSETOF(type,member) );})
测试代码:
#include <stdio.h>
#define OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - OFFSETOF(type,member) );})
void main(void)
{
struct test{
int a;
char b;
unsigned int c;
unsigned char d;
};
struct test tmp = {5, 'x', 2, 1};
struct test *ptmp = NULL;
ptmp = container_of(&tmp.c, struct test, c);
printf("a=%d, b=%c, c=%d, d=%d \n", ptmp->a, ptmp->b, ptmp->c, ptmp->d);
}
结果:
-
内核kfifo
kfifo最终版(参考linux-5.14.7)
-
取余%转化为与&
若b为2的n次幂,则a%b可转化为a&(b-1)。 与& 的效率比 取余% 高。 -
宏定义中的do{…}while(0)的使用
举例:
if (NULL != p)
SAFE_FREE(p);
else
...
#define SAFE_FREE(p) do{free(p); p=NULL;} while(0)
会被展开为:
if (NULL != p)
do {
free(p);
p=NULL;
} while(0);
else
...
若不用do{…}while(0):
#define SAFE_FREE(p) {free(p); p=NULL;}
会被展开为:
if (NULL != p)
{
free(p);
p=NULL;
}
;
else
...
这样会造成else悬挂,else分支没有对应的if了,编译无法提供。
-
__func__
C99已支持__func__宏,意味着字符串,可使用%s print出。 -
宏定义中typeof((inp) + 1) __tmp = (inp); 保证传入的inp为指针类型
使用typeof((fifo) + 1) __tmp = (fifo);
保证传入的fifo为指针类型(在编译阶段即可检查出来,而不是程序运行后发生奇奇怪怪的错误)
宏定义实现的函数和真正的函数的最大的区别为:真正的函数在编译时会做参数类型检查,若调用函数的入参类型不对,编译阶段就会报错;使用宏定义是实现不了参数类型检查的。所以得使用诸如上述的特殊技巧进行编译阶段的检查。