C语言技巧(转的)

C语言经常让人以为它所能表达的东西很是有限。它不具备相似第一级函数和模式匹配这样的高级功能。可是C很是简单,而且仍然有一些很是有用的语法技巧和功能,只是没有多少人知道罢了。linux

 

指定的初始化
不少人都知道像这样来静态地初始化数组:express


intfibs[] = {1, 1, 2, 3, 5};
C99标准实际上支持一种更为直观简单的方式来初始化各类不一样的集合类数据(如:结构体,联合体和数组)。编程

 

数组
咱们能够指定数组的元素来进行初始化。这很是有用,特别是当咱们须要根据一组#define来保持某种映射关系的同步更新时。来看看一组错误码的定义,如:数组

/* Entries may not correspond to actual numbers. 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。
 
  
 oop


结构体与联合体
 
用结构体与联合体的字段名称来初始化数据是很是有用的。假设咱们定义:ui

structpoint {
    intx;
    inty;
    intz;
}
 
而后咱们这样初始化structpoint:
1
structpoint p = {.x = 3, .y = 4, .z = 5};
当咱们不想将全部字段都初始化为0时,这种做法能够很容易的在编译时就生成结构体,而不须要专门调用一个初始化函数。 www.2cto.comspa

对联合体来讲,咱们可使用相同的办法,只是咱们只用初始化一个字段。索引

 
宏列表
C中的一个惯用方法,是说有一个已命名的实体列表,须要为它们中的每个创建函数,将它们中的每个初始化,并在不一样的代码模块中扩展它们的名字。这在Mozilla的源码中常常用到,我就是在那时学到这个技巧的。例如,在我去年夏天工做的那个项目中,咱们有一个针对每一个命令进行标记的宏列表。其工做方式以下:rem


#define FLAG_LIST(_)                   \
    _(InWorklist)                      \
    _(EmittedAtUses)                   \
    _(LoopInvariant)                   \
    _(Commutative)                     \
    _(Movable)                         \
    _(Lowered)                         \
    _(Guard)
它定义了一个FLAG_LIST宏,这个宏有一个参数称之为 _ ,这个参数自己是一个宏,它可以调用列表中的每一个参数。举一个实际使用的例子可能更能直观地说明问题。假设咱们定义了一个宏DEFINE_FLAG,如:


#define DEFINE_FLAG(flag) flag,
   enumFlag {
       None = 0,
       FLAG_LIST(DEFINE_FLAG)
       Total
   };
#undef DEFINE_FLAG
对FLAG_LIST(DEFINE_FLAG)作扩展可以获得以下代码:


enumFlag {
        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以下:


enumFlag {
        None = 0,
        InWorklist,
        EmittedAtUses,
        LoopInvariant,
        Commutative,
        Movable,
        Lowered,
        Guard,
        Total
    };
接着,咱们可能要定义一些访问函数,这样才能更好的使用flag列表:

#define FLAG_ACCESSOR(flag) \
boolis##flag()const{\
    returnhasFlags(1 << flag);\
}\
voidset##flag() {\
    JS_ASSERT(!hasFlags(1 << flag));\
    setFlags(1 << flag);\
}\
voidsetNot##flag() {\
    JS_ASSERT(hasFlags(1 << flag));\
    removeFlags(1 << flag);\
}
 
FLAG_LIST(FLAG_ACCESSOR)
#undef FLAG_ACCESSOR
一步步的展现其过程是很是有启发性的,若是对它的使用还有不解,能够花一些时间在gcc –E上。

 
编译时断言
这实际上是使用C语言的宏来实现的很是有“创意”的一个功能。有些时候,特别是在进行内核编程时,在编译时就可以进行条件检查的断言,而不是在运行时进行,这很是有用。不幸的是,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)为零值,那么代码将能顺利地编译,并生成一个大小为零的结构体。若是(condition)结果为0(在C真为假),那么在试图生成一个负大小的结构体时,就会产生编译错误。

它的使用很是简单,若是任何某假设条件可以静态地检查,那么它就能够在编译时断言。例如,在上面提到的标志列表中,标志集合的类型为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 } )
由于位宽为负,因此能够肯定,若是标志的数量超过了咱们指派的空间,那么编译将会失败。


摘自  Michael Xiao的程序人生
相关文章
相关标签/搜索