文章目录
指定初始化
除了我们常用的静态初始化数组:
int fibs[] = {1, 1, 2, 3, 5};
C99 标准支持一种更为直观的方式来初始化:结构体、联合体、数组;
初始化数组
当我们需要根据一组 #define
来保持某种映射关系的同步更新时,我们可以如此初始化数组:
// 一组错误码的定义
#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;
}
我们可以如此初始化:
struct point p = {x = 3, y = 4, z = 5};
当我们不想将所有字段都初始化为0时,这种作法可以很容易的在编译时就生成结构体,而不需要专门调用一个初始化函数。
对联合体来说,我们可以使用相同的办法,只是我们只用初始化一个字段。
利用预处理进行编译断言
我们通常希望程序在编译的时候能够进行条件检查的断言,而不是在运行时。但是,C99 标准还不支持任何编译时的断言。我们可以利用预处理生成代码,实现编译阶段的断言。这些端木只有在某些条件成立时,才会通过编译。
通常的做法是建立一个大小为负大小的数组或结构体。
/*Force a compilation error if condition is false, but also produce a result
* (of value 0 and type size_t), so it can be used e.g. in a structure
* initializer (or wherever else comma expressions aren't permitted). */
/* Linux calls these BUILD_BUG_ON_ZERO/_NULL, which is rather misleading. */
#define STATIC_ZERO_ASSERT(condition) (sizeof(struct { int:-!(condition); }) )
#define STATIC_NULL_ASSERT(condition) ((void *)STATIC_ZERO_ASSERT(condition) )
/* Force a compilation error if condition is false */
#define STATIC_ASSERT(condition) ((void)STATIC_ZERO_ASSERT(condition))
- 如果条件的计算结果(
condition
)为非零值 (即 C 中的真值),代码将顺利编译; - 如果条件的计算结果(
condition
)为零值,那么试图生成一个负大小的结构图,就会产生编译错误;
如果任何某假设条件能够静态的编译,那么它就可以在编译的时候断言;
假设,我们要判断 Total
的数量是否小于 32
,可以如下使用:
STATIC_ASSERT(Total <= 32)
扩展为:
(void)sizeof(struct { int:-!(Total <= 32) })
Total
确实小于 32:那么-!(Total <= 32)
等于0,所以这行代码相当于:
```(void)sizeof(struct { int: 0 }) // `这是合法的``;Total
大于 32:那么-!(Total <= 32)
等于 -1,所以这行代码为:
(void)sizeof(struct { int: -1 } )
因为位宽位负数,编译肯定会失败;
宏列表
C 中的一个惯用方法:有一个已命名的实体列表,需要为它们中的每个建立一个函数,将它们中的每个初始化,并在不同的代码模块中扩展它们的名字。其工作方式如下:
#define FLAG_LIST(_) \
_(InWorklist) \
_(EmittedAtUses) \
_(LoopInvariant) \
_(Commutative) \
_(Movable) \
_(Lowered) \
_(Guard)
定义了一个带参的宏 FLAG_LIST(_)
,这个参数本身也是一个宏名,上述的列表如何使用呢?
宏列表在枚举中的使用
假设我们定义了一个带参的宏名:
#define DEFINE_FLAG(flag) flag,
enum Flag {
None = 0,
FLAG_LIST(DEFINE_FLAG)
Total
};
#undef DEFINE_FLAG
对于 enum 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
};
利用宏列表生成函数
#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
- 第一层扩展:
FLAG_ACCESSOR(InWorklist)
FLAG_ACCESSOR(EmittedAtUses)
FLAG_ACCESSOR(LoopInvariant)
FLAG_ACCESSOR(Commutative)
FLAG_ACCESSOR(Movable)
FLAG_ACCESSOR(Lowered)
FLAG_ACCESSOR(Guard)
- 第二次扩展:只扩展第一个宏:
bool is##InWorklist() const {
return hasFlags(1 << InWorklist);
}
void set##InWorklist() {
JS_ASSERT(!hasFlags(1 << InWorklist));
setFlags(1 << InWorklist);
}
void setNot##InWorklist() {
JS_ASSERT(hasFlags(1 << InWorklist));
removeFlags(1 << InWorklist);
}