一个、宏列表
当这个问题面临:
有一个标志变量。位代表对应的含义。
我们须要提供一组函数来訪问设置这些位。可是对于每一个标记位的操作函数都是相似的。若有32个位,难道要搞32套相似的操作函数么?
你或许会说,用一套操作函数。依据传入的參数来推断对哪个位操作。这样固然可行,可是
①不够直观。比如訪问Movable标记位,对于用户来说,is Movable()是非常自然的方式,而我们仅仅能提供这种接口isFlag(Movable)
②扩展性差。
若以后添加删改标记位,则须要更改isFlag等函数的代码。
我们想有这种设计:
在头文件的宏定义中增删标记位的宏。我们为每一个标记位设计的操作函数名就自己主动更改。添加的标记位也自己主动添加一套操作函数。删除的标记位也自己主动减去一套操作函数。
这种设计就太爽了!
但怎样实现呢?
首先,每一个标记位的宏名一变,我们的操作函数名也要对应改变,这时我们能够想到用带參宏。并用宏的##符。把两个字符串合在一起。
(使它们能被宏替换掉)
#define FLAG_ACCESSOR(flag)
bool is##flag() const {
return hasFlags(1 << flag);
}
void set##flag() {
JS_ASSERT(!hasFlags(1 << flag));
setFlags(1 << flag);
}
void setNot##flag() {
JS_ASSERT(hasFlags(1 << flag));
removeFlags(1 << flag);
}
[这一步一般人都能想到的。
]
这样,FLAG_ACCESSOR(Movable)就可得到操作Movable标记位的三个函数:is Movable(),set Movable()。setNot Movable()
可是,难道有多少个标记位,我们就要写多少个FLAG_ACCESSOR(flag)么?
怎样用一个式子来扩展成多个种的FLAG_ACCESSOR(flag),提取共性,因为这多个FLAG_ACCESSOR(flag),flag是不同的,宏函数名是同样的。故用宏列表:
#define FLAG_LIST(_)
_(InWorklist)
_(EmittedAtUses)
_(LoopInvariant)
_(Commutative)
_(Movable)
_(Lowered)
_(Guard)
这样一个式子:FLAG_LIST(FLAG_ACCESSOR)就搞定了。
可是,另一个问题,我们还未定义InWorklist、EmittedAtUses、LoopInvariant等。须要再用宏来定义这些标记位的名字。
比如:
#define InWorklist 1
#define EmittedAtUses 2
……
这样以来,若以后我们增改标记位的名字 就须要改动两处地方了:宏列表、标记名的宏定义。
我们想要的最好的设计是,仅仅改变一处 处处跟着一起改变。
[yang]若是有新的标记位加入我们仅仅在#define FLAG_LIST(_) 中加入一项就好了。比如,_(Visited) 自己主动加入#define Visited 8。
自己主动加入一项宏定义难以实现,那我们考虑有没有替代方案。观察发现此宏定义都是定义的数字,而枚举也有相同的功能。
这样。我们把这些展开的位标记名放在enum枚举中。让其自己主动赋上1,2,3……等数值。而不必用宏定义一个一个地定义。
如今问题变为:怎样使我们在#defineFLAG_LIST(_) 中加入一项,enum枚举中就自己主动加入对应的一项?
我们仅仅有把FLAG_LIST(_)放入enum枚举中,这样才干一增俱增。
若宏列表:
#define FLAG_LIST(_)
_(InWorklist)
_(EmittedAtUses)
_(LoopInvariant)
能再变为:
InWorklist
EmittedAtUses
LoopInvariant
就好了。
这样,我们在#defineFLAG_LIST(_) 中加入一项_(Visited)。
则enum中自己主动加入Visited。
也就是_(InWorklist)怎样展开成InWorklist。这个非常easy:#define DEFINE_FLAG(flag)flag,
其详细实现方式例如以下:
#define FLAG_LIST(_)
_(InWorklist)
_(EmittedAtUses)
_(LoopInvariant)
_(Commutative)
_(Movable)
_(Lowered)
_(Guard)
它定义了一个FLAG_LIST宏。这个宏有一个參数称之为 _ ,这个參数本身是一个宏,它可以调用列表中的每一个參数。举一个实际使用的样例可能更能直观地说明问题。
如果我们定义了一个宏DEFINE_FLAG,如:
#define DEFINE_FLAG(flag) flag, //注意flag后有逗号
enum Flag {
None = 0,
FLAG_LIST(DEFINE_FLAG)
Total
};
#undef DEFINE_FLAG
对FLAG_LIST(DEFINE_FLAG)做扩展可以得到例如以下代码:
enum Flag {
None = 0,
DEFINE_FLAG(InWorklist)
DEFINE_FLAG(EmittedAtUses)
DEFINE_FLAG(LoopInvariant)
DEFINE_FLAG(Commutative)
DEFINE_FLAG(Movable)
DEFINE_FLAG(Lowered)
DEFINE_FLAG(Guard)
Total
};
接着,对每一个參数都扩展DEFINE_FLAG宏,这样我们就得到了enum例如以下:
enum Flag {
None = 0,
InWorklist,
EmittedAtUses,
LoopInvariant,
Commutative,
Movable,
Lowered,
Guard,
Total
};
接着。我们可能要定义一些訪问函数,这样才干更好的使用flag列表:
#define FLAG_ACCESSOR(flag)
bool is##flag() const {
return hasFlags(1 << flag);
}
void set##flag() {
JS_ASSERT(!hasFlags(1 << flag));
setFlags(1 << flag);
}
void setNot##flag() {
JS_ASSERT(hasFlags(1 << flag));
removeFlags(1 << flag);
}
FLAG_LIST(FLAG_ACCESSOR)
#undef FLAG_ACCESSOR
(这样。我们仅仅在宏列表一处更改增删位操作就可以。)
【总结:yang】
一步步的展示其过程是很有启示性的,假设对它的使用还有不解,能够花一些时间在gcc –E上。
【宏列表的长处有:能够把一个式子扩展成多个式子,且非常easy扩展。仅仅要再添加列表项就可以。】
二、指定的初始化
非常多人都知道像这样来静态地初始化数组:
int fibs[] = {1,2,3,4,5} ;
C99标准实际上支持一种更为直观简单的方式来初始化各种不同的集合类数据(如:结构体。联合体和数组)。
数组的初始化
我们能够指定数组的元素来进行初始化。这很实用,特别是当我们须要依据一组#define来保持某种映射关系的同步更新时。来看看一组错误码的定义。如:
/* Entries may not correspond to actualnumbers. Some entries omitted. */
#define EINVAL 1
#define ENOMEM 2
#define EFAULT 3
/* ... */
#define E2BIG 7
#define EBUSY 8
/* ... */
#define ECHILD 12
/* ... */
如今,如果我们想为每一个错误码提供一个错误描写叙述的字符串。为了确保数组保持了最新的定义,不管头文件做了不论什么改动或增补。我们都能够用这个数组指定的语法。
char *err_strings[] = {
[0] = "Success",
[EINVAL] = "Invalid argument",
[ENOMEM] = "Not enough memory",
[EFAULT] = "Bad address",
/* ... */
[E2BIG ] = "Argument list too long",
[EBUSY ] = "Device or resource busy",
/* ... */
[ECHILD] = "No child processes"
/* ... */
};
这样就能够静态分配足够的空间,且保证最大的索引是合法的,同一时候将特殊的索引初始化为指定的值,并将剩下的索引初始化为0。
结构体与联合体
用结构体与联合体的字段名称来初始化数据是很实用的。如果我们定义:
struct point {
int x;
int y;
int z;
} ;
然后我们这样初始化structpoint:
struct point p = {.x = 1, .z = 3}; //x为1,y为0,z为3
当我们不想将全部字段都初始化为0时,这样的作法能够非常easy的在编译时就生成结构体,而不须要专门调用一个初始化函数。
对联合体来说。我们能够使用同样的办法,仅仅是我们仅仅用初始化一个字段。
三、编译时断言
这事实上是使用C语言的宏来实现的很有“创意”的一个功能。
有些时候,特别是在进行内核编程时,在编译时就行进行条件检查的断言,而不是在执行时进行,这很实用。
不幸的是。C99标准还不支持不论什么编译时的断言。
可是,我们能够利用预处理来生成代码,这些代码仅仅有在某些条件成立时才会通过编译(最好是那种不做实际功能的命令)。有各种各样不同的方式都能够做到这一点。通常都是建立一个大小为负的数组或结构体。
最经常使用的方式例如以下:
#define STATIC_ZERO_ASSERT(condition)(sizeof(struct { int:-!(condition); }) )
#define STATIC_NULL_ASSERT(condition)((void *)STATIC_ZERO_ASSERT(condition) )
/* Force a compilation error if conditionis false */
#define STATIC_ASSERT(condition)((void)STATIC_ZERO_ASSERT(condition))
假设(condition)计算结果为一个非零值(即C中的真值),即! (condition)为零值。那么代码将能顺利地编译,并生成一个大小为零的结构体。
假设(condition)结果为0(在C真为假)。那么在试图生成一个负大小的结构体时。就会产生编译错误。
它的使用很easy,如果不论什么某如果条件能够静态地检查,那么它就能够在编译时断言。比如。在上面提到的标志列表中,标志集合的类型为uint32_t,所以,我们能够做下面断言:
STATIC_ASSERT(Total <= 32)
它扩展为:
(void)sizeof(struct { int:-!(Total <=32) })
如今,如果Total<=32。那么-!(Total <= 32)等于0,所以这行代码相当于:
(void)sizeof(struct { int: 0 })
这是一个合法的C代码。如今如果标志不止32个,那么-!(Total <= 32)等于-1,所以这时代码就相当于:
(void)sizeof(struct { int: -1 } )
由于位宽为负。所以能够确定,假设标志的数量超过了我们指派的空间,那么编译将会失败。
四、在指针中隐藏数据
(这个技术有点变态。大家看看就好)
编写 C 语言代码时。指针无处不在。我们能够略微额外利用指针。在它们内部暗中存储一些额外信息。为实现这一技巧,我们利用了数据在内存中的自然对齐特性。
如果系统中整型数据和指针大小均为 4 字节。
则指针的数值(即当中包括的地址值)。都是4的整数倍。也就是说其二进制数都是以 00 结尾。那么这 2 比特没有承载不论什么信息。
所以就有人脑动大开。利用这两个比特存点信息。在使用指针之前用位操作的方式存储2bit信息到此指针。当要对指针进行解引用操作时,把其原先值提取出来。
void put_data(int *p, unsigned int data) { assert(data < 4); *p |= data; } unsigned int get_data(unsigned int p) { return (p & 3); } void cleanse_pointer(int *p) { *p &= ~3; } int main(void) { unsigned int x = 701; unsigned int p = (unsigned int) &x; printf("Original ptr: %un", p); //把3存储到指针中 put_data(&p, 3); printf("ptr with data: %un", p); printf("data stored in ptr: %un", get_data(p)); //获取指针中的数据3 cleanse_pointer(&p); //在解引用指针前,把隐藏的2bit数据抹掉,恢复其原值 printf("Cleansed ptr: %un", p); printf("Dereferencing cleansed ptr: %un", *(int*)p); return 0; }
这也太变态了吧,连这2个bit都不放过。如今是21世纪了。我们还缺这点内存么?
只是,在实际中还真有应用:Linux 内核中红黑树的实现。
树结点定义:
struct rb_node {
unsigned long __rb_parent_color;
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
此处 unsigned long __rb_parent_color 存储了例如以下信息:
父节点的地址,结点的颜色
色彩的表示用 0 代表红色。1 代表黑色。
和前面的样例一样,该数据隐藏在父指针“没用的”比特位中。
父指针和色彩信息的获取:
/* in rbtree.h */
#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3))
/* in rbtree_augmented.h */
#define __rb_color(pc) ((pc) & 1)
#define rb_color(rb) __rb_color((rb)->__rb_parent_color)
【參考】
http://blog.jobbole.com/16035/
版权声明:本文博客原创文章,博客,未经同意,不得转载。