C语言一些实用技巧
Posted Frey_Liu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言一些实用技巧相关的知识,希望对你有一定的参考价值。
C语言一些实用技巧
指定的初始化
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。
结构体与联合体
用结构体与联合体的字段名称来初始化数据是非常有用的。假设我们定义:
struct point
int x;
int y;
int z;
然后,我们这样初始化struct point:
struct point p = .x = 3, .y = 4, .z = 5;
当我们不想将所有字段都初始化为0时,这种作法可以很容易的在编译时就生成结构体,而不需要专门调用一个初始化函数。
对联合体来说,我们可以使用相同的办法,只是我们只用初始化一个字段。
宏的使用
宏列表
C中的一个惯用方法,是说有一个已命名的实体列表,需要为它们中的每一个建立函数,将它们中的每一个初始化,并在不同的代码模块中扩展它们的名字。这在Mozilla的源码中经常用到,我就是在那时学到这个技巧的。例如,在我去年夏天工作的那个项目中,我们有一个针对每个命令进行标记的宏列表。其工 作方式如下:
#define FLAG_LIST(_) \\
_(InWorklist) \\
_(EmittedAtUses) \\
_(LoopInvariant) \\
_(Commutative) \\
_(Movable) \\
_(Lowered) \\
_(Guard)
它定义了一个FLAG_LIST宏,这个宏有一个参数称之为 _ ,这个参数本身是一个宏,它能够调用列表中的每个参数。举一个实际使用的例子可能更能直观地说明问题。假设我们定义了一个宏DEFINE_FLAG,比如:
#define DEFINE_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
一步步的展示其过程是非常有启发性的,如果对它的使用还有不解,可以花一些时间在gcc –E上。
宏列表应用于C程序中管理标志位的例子
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef signed char int8_t;
typedef int int16_t;
#define true 1
#define false 0
//宏列表
#define TAG_LIST(tag) \\
tag(Run)\\
tag(Alarm)\\
tag(Online)\\
tag(TimerOver)
//枚举处理
#define DEFINE_TAG(_tag) _tag,
enum Flag
None = 0,
TAG_LIST(DEFINE_TAG)
EmMAX
;
#undef DEFINE_TAG
//位定义变量
uint16_t SysFlag = 0x0000;
//通用方法定义
uint8_t GetFlags(uint16_t mask)
return ((SysFlag & mask) != 0)? true:false;
void SetFlags(uint16_t mask)
SysFlag |= mask;
void ClrFlags(uint16_t mask)
SysFlag &= ~mask;
//自动生成三类函数定义
#define FLAG_Operater(flag) \\
uint8_t get##flag() \\
return GetFlags(1 << flag);\\
\\
void set##flag() \\
SetFlags(1 << flag);\\
\\
void clr##flag() \\
ClrFlags(1 << flag);\\
//反向函数关联
TAG_LIST(FLAG_Operater)
int main(int argc, char *argv[])
setRun();
setAlarm();
if(getAlarm() == true)
printf("set \\r\\n");
else
printf("clr \\r\\n");
return 0;
编译时断言
这其实是使用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 )
因为位宽为负,所以可以确定,如果标志的数量超过了我们指派的空间,那么编译将会失败。
静态断言
#define STATIC_ASSERT(expr) (\\
(void)sizeof(char[1-2*!!!(expr)]))
当然还有其他实现方法,但这种无疑是最简单、最通用的方法。
获取偏移量
define offsetof(type, member) (\\
(size_t)&((type*)0->menber) )
一般来说,这个宏会在<stddef.h>文件中被定义,当然其实现形式会随编译器的不同而变化
获取容器地址
#define container_of(ptr, type, member) (\\
(type *)( (char *)(ptr) - offsetof(type,member) ) )
这个宏常见于Linux内核,而它通常的定义如下:
/**
* 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) );)
仔细琢磨便可发现,上述定义中的第一个语句实际上没有实现任何功能。而且,上述定义中使用了GNUC的拓展语法,不属于标准C语言的范畴。然而,第一个定义却是完全符合标准C语言语法,但相较于第二种定义失去了类型检查的功能,变得更加不安全。
获取数组元素数目
#define ARRAY_SIZE(a) ( sizeof(a)/sizeof((a)[0]) )
同样来源于Linux内核的宏,同样为了适应标准C做了阉割。在gcc环境下,更加安全的定义如下:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) \\
+sizeof(typeof(int[1-2*!!__builtin_types_compatible_p(typeof(arr),\\
typeof(&arr[0]))]))*0)
同理,利用GNUC的typeof关键字和内建函数,判断arr和&arr[0]类型是否相同,相同则会导致数组长度为负数,实现静态断言,避免在这里错误地使用指针作为宏的参数。所以,当你使用这个宏的时候,请务必谨慎!同时还要注意,这个宏的名称有歧义,我更乐意将它叫做member_of或者number_of因为它返回的是数组的元素个数,而不是数组的大小。
头文件保护符
#ifndef __XXX_H__
#define __XXX_H__
#endif
// 拓展有:
#ifndef XXX_GLOBAL
#define XXX_EXT extern
#else
#define XXX_EXT
#endif
#ifndef XXX_GLOBAL
#define INIT(val, init) (val)
#else
#define INIT(val, init) (val) = (init)
#endif
/*
__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,那么加入extern "C"和处理其中的代码。
*/
#ifdef __cplusplus
extern "C"
#endif
//一段代码 ......
#ifdef __cplusplus
#endif
还是比较有用的,都是条件编译的灵活运用。
符号转字符串
#define _STR(x) #x
//这个配合字符串的拼接,能够完成一些相当便利的操作,非常便利了。如:
#define sys_call(n) __asm volatile("svc #"#n)
符号拼接
#define _str_spice(tkn1, tkn2) tkn1##tkn2
#define str_spice(tkn1, tkn2) _str_spice(tkn1, tkn2)
泛型编程
借助宏,还可以实现泛型编程,实现c++当中的模版的部分功能。示例如下:
/*
* heap.h
*
* Author: SMS
*/
#ifndef __HEAP_H__
#define __HEAP_H__
#include <stdint.h>
#include <stdbool.h>
#define _str_spice(tkn1, tkn2) tkn1##tkn2
#define str_spice(tkn1, tkn2) _str_spice(tkn1, tkn2)
#define __template(type, tplt) str_spice(type, str_spice(_, tplt))
#ifdef __cplusplus
extern "C"
#endif /* __CPLUSPLUS */
#ifndef heap_type
#define heap_type int
#endif
typedef bool(*compare_t)(heap_type*, heap_type*);
static inline void
__template(heap_type, heap_swap)(heap_type *a, heap_type *b)
heap_type t;
t = *a;
*a = *b;
*b = t;
static inline void
__template(heap_type, heap_build)(heap_type a[],
compare_t func,
unsigned bgn,
unsigned end)
heap_type t = a[bgn];
unsigned i;
for(i=(bgn<<1)+1; i<=end; bgn=i, i=(i<<1)+1)
if(i<end && !func(&a[i], &a[i+1]))
i++;
if(func(&t, &a[i]))
break;
a[bgn] = a[i];
a[i] = t;
static inline void
__template(heap_type, heap_adjast)(heap_type a[],
compare_t func,
unsigned bgn,
unsigned end)
heap_type t = a[end];
unsigned i;
for(i=(end-1)>>1; i>=bgn && end!=bgn; end=i, i=(i-1)>>1)
if(!func(&t, &a[i]))
break;
a[end] = a[i];
a[i] = t以上是关于C语言一些实用技巧的主要内容,如果未能解决你的问题,请参考以下文章
Luogu P5556 圣剑护符(线性基,树链剖分,线段树)