你最喜欢的C编程技巧是什么?

例如,我最近在linux内核中遇到了这个问题:

 / *如果条件为真,强制编译错误* /
 #define BUILD_BUG_ON(condition)((void)sizeof(char [1  -  2 * !!(condition)]))

所以,在你的代码中,如果你有一些必须的结构,比如8字节的倍数,也许是因为硬件限制,你可以这样做:

 BUILD_BUG_ON((sizeof(struct mystruct)%8)!= 0);

除非struct mystruct的大小是8的倍数,否则不会编译,如果是8的倍数,则根本不生成运行时代码。

我所知道的另一个技巧是从“graphicsgem”这本书,它允许一个单一的头文件来声明和初始化一个模块中的variables,而在其他模块使用该模块,只是声明他们为extern。

 #ifdef DEFINE_MYHEADER_GLOBALS
 #define GLOBAL
 #define INIT(x,y)(x)=(y)
 #其他
 #define GLOBAL extern
 #define INIT(x,y)
 #万一

 GLOBAL int INIT(x,0);
 GLOBAL int somefunc(int a,int b);

有了这个,定义x和somefunc的代码就可以:

 #define DEFINE_MYHEADER_GLOBALS
 #include“the_above_header_file.h”

而仅仅使用x和somefunc()的代码会执行:

 #include“the_above_header_file.h”

所以你得到一个头文件,它声明了全局variables和函数原型的实例,以及相应的extern声明。

那么,你最喜欢的C编程技巧是什么?

C99提供了一些非常酷的东西,使用匿名数组:

删除无意义的variables

 { int yes=1; setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); } 

 setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int)); 

传递可变数量的参数

 void func(type* values) { while(*values) { x = *values++; /* do whatever with x */ } } func((type[]){val1,val2,val3,val4,0}); 

静态链接列表

 int main() { struct llist { int a; struct llist* next;}; #define cons(x,y) (struct llist[]){{x,y}} struct llist *list=cons(1, cons(2, cons(3, cons(4, NULL)))); struct llist *p = list; while(p != 0) { printf("%d\n", p->a); p = p->next; } } 

任何我相信许多其他很酷的技术,我没有想到。

在阅读Quake 2源代码时,我想到了这样的东西:

 double normals[][] = { #include "normals.txt" }; 

(或多或less,我没有代码方便现在检查它)。

从那以后,预处理器的创造性使用的新世界在我眼前展开。 我不再只包含头文件,而是包含整个代码块(它大大提高了可重用性):-p

感谢John Carmack! 的xD

我喜欢使用= {0}; 初始化结构而不需要调用memset。

 struct something X = {0}; 

这将初始化结构(或数组)的所有成员为零(但不是任何填充字节 – 使用memset,如果您需要也将零)。

但是您应该意识到, 对于大型dynamic分配的结构,存在一些问题 。

如果我们正在谈论C技巧,我最喜欢的就是Duff的Device for loop展开! 我只是在等待合适的机会来为我实际使用它在愤怒…

使用__FILE____LINE__进行debugging

 #define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__); 

在C99

 typedef struct{ int value; int otherValue; } s; s test = {.value = 15, .otherValue = 16}; /* or */ int a[100] = {1,2,[50]=3,4,5,[23]=6,7}; 

一旦我的队友和我重新定义返回find一个棘手的堆栈腐败错误。

就像是:

 #define return DoSomeStackCheckStuff, return 

我喜欢有一个dynamic大小的对象的“结构黑客”。 这个网站也很好地解释了它(尽pipe它们指的是C99版本,你可以在其中写入“str []”作为结构的最后一个成员)。 你可以像这样做一个string“对象”:

 struct X { int len; char str[1]; }; int n = strlen("hello world"); struct X *string = malloc(sizeof(struct X) + n); strcpy(string->str, "hello world"); string->len = n; 

在这里,我们已经在堆上分配了一个types为X的结构,这个types的大小是一个int(对于len),加上“hello world”的长度加上1(因为str 1包含在sizeof(X)中。

当你想要在同一个块中的某个可变长度数据之前有一个“标题”时,它通常是有用的。

面向对象的代码与C,通过模拟类。

只需创build一个结构体和一组函数,并将该结构体的指针作为第一个参数。

代替

 printf("counter=%d\n",counter); 

使用

 #define print_dec(var) printf("%s=%d\n",#var,var); print_dec(counter); 

使用一个愚蠢的macros技巧,使logging定义更容易维护。

 #define COLUMNS(S,E) [(E) - (S) + 1] typedef struct { char studentNumber COLUMNS( 1, 9); char firstName COLUMNS(10, 30); char lastName COLUMNS(31, 51); } StudentRecord; 

为了创build一个只读的模块,除了在下面声明的模块之外:

 // Header1.h: #ifndef SOURCE1_C extern const int MyVar; #endif 

 // Source1.c: #define SOURCE1_C #include Header1.h // MyVar isn't seen in the header int MyVar; // Declared in this file, and is writeable 

 // Source2.c #include Header1.h // MyVar is seen as a constant, declared elsewhere 

位移仅定义为移位量31(在32位整数上)。

如果你想计算一个需要更高偏移值的换档,你会怎么做? 以下是Theora vide-codec的function:

 unsigned int shiftmystuff (unsigned int a, unsigned int v) { return (a>>(v>>1))>>((v+1)>>1); } 

或更可读:

 unsigned int shiftmystuff (unsigned int a, unsigned int v) { unsigned int halfshift = v>>1; unsigned int otherhalf = (v+1)>>1; return (a >> halfshift) >> otherhalf; } 

上面显示的方式执行任务比使用像这样的分支要快得多:

 unsigned int shiftmystuff (unsigned int a, unsigned int v) { if (v<=31) return a>>v; else return 0; } 

声明数组的指针来实现有限状态机的函数。

 int (* fsm[])(void) = { ... } 

最令人满意的优势是,强制每个刺激/状态检查所有代码path是很简单的。

在一个embedded式系统中,我经常会映射一个ISR来指向这样一个表格,并且根据需要(ISR外部的)指示它。

另一个不错的预处理器“技巧”是使用“#”字符打印debuggingexpression式。 例如:

 #define MY_ASSERT(cond) \ do { \ if( !(cond) ) { \ printf("MY_ASSERT(%s) failed\n", #cond); \ exit(-1); \ } \ } while( 0 ) 

编辑:下面的代码只适用于C ++。 感谢smcameron和Evan Teran。

是的,编译时间总是很好。 它也可以写成:

 #define COMPILE_ASSERT(cond)\ typedef char __compile_time_assert[ (cond) ? 0 : -1] 

我不会把它称为最喜欢的伎俩,因为我从来没有使用它,但提到达夫的设备提醒我这篇关于在C中实现协程的文章 。它总是给我一个笑声,但我相信它可以有一段时间是有用的。

 #if TESTMODE == 1 debug=1; while(0); // Get attention #endif 

while(0); 对程序没有任何影响,但是编译器会发出一个关于“这个什么也不做”的警告,这足以让我去看看这个违规的行,然后看看我想引起注意的真正原因。

我是xor黑客的粉丝:

交换2个没有第三个温度指针的指针:

 int * a; int * b; a ^= b; b ^= a; a ^= b; 

或者我真的很喜欢只有一个指针的异或链表。 (http://en.wikipedia.org/wiki/XOR_linked_list);

链表中的每个节点都是前一个节点的Xor和下一个节点。 为了向前遍历,节点的地址按以下方式find:

 LLNode * first = head; LLNode * second = first.linked_nodes; LLNode * third = second.linked_nodes ^ first; LLNode * fourth = third.linked_nodes ^ second; 

等等

或者往后走:

 LLNode * last = tail; LLNode * second_to_last = last.linked_nodes; LLNode * third_to_last = second_to_last.linked_nodes ^ last; LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last; 

等等

虽然不是非常有用(你不能从任意节点开始遍历),但我发现它非常酷。

这本书来自“足够的绳子在脚下自杀”的书:

在头文件中声明

 #ifndef RELEASE # define D(x) do { x; } while (0) #else # define D(x) #endif 

在你的代码放置testing语句如:

 D(printf("Test statement\n")); 

do / while有助于防止macros的内容扩展为多个语句。

如果不使用编译器的“-D RELEASE”标志,只会打印该语句。

那么你可以例如。 将标志传递给你的makefile文件等

不知道这是如何工作在Windows中,但在*尼克斯运作良好

Rusty实际上在ccan中生成了一整套构build条件,查看构build断言模块:

 #include <stddef.h> #include <ccan/build_assert/build_assert.h> struct foo { char string[5]; int x; }; char *foo_string(struct foo *foo) { // This trick requires that the string be first in the structure BUILD_ASSERT(offsetof(struct foo, string) == 0); return (char *)foo; } 

在实际的头文件中还有许多其他有用的macros,这些macros很容易放置到位。

我尝试着,尽我所能去抵制黑暗的一面(和预处理器的滥用),主要是坚持内联函数,但是我喜欢那些你所描述的聪明,有用的macros。

编程和编写固体代码 的实践有两个很好的源代码书。 其中一个(我不记得是哪一个)说:优先枚举#define在哪里可以,因为枚举被编译器检查。

不是特定于C,但我一直喜欢XOR运算符。 它可以做的一件很酷的事情是“没有临时值的交换”:

 int a = 1; int b = 2; printf("a = %d, b = %d\n", a, b); a ^= b; b ^= a; a ^= b; printf("a = %d, b = %d\n", a, b); 

结果:

a = 1,b = 2

a = 2,b = 1

参见“隐藏的C特性”问题。

我喜欢在list container_of使用container_of的概念。 基本上,你不需要为列表中的每个结构指定nextlast字段。 而是,您将列表结构标题附加到实际链接的项目。

看一下include/linux/list.h中的实例。

我认为使用userdata指针是相当整洁的。 时下stream行的时尚。 这不是一个Cfunction,但在C中使用相当简单

我使用X-Macros来让预编译器生成代码。 它们对于在一个地方定义错误值和相关的错误string特别有用,但是它们可以远远超出这个范围。

我们的代码库有个类似的技巧

 #ifdef DEBUG #define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__) void * my_malloc_debug(int amt, char* file, int line) #else void * my_malloc(int amt) #endif { //remember file and line no. for this malloc in debug mode } 

这允许在debugging模式下跟踪内存泄漏。 我一直认为这很酷。

有趣的macros:

 #define SOME_ENUMS(F) \ F(ZERO, zero) \ F(ONE, one) \ F(TWO, two) /* Now define the constant values. See how succinct this is. */ enum Constants { #define DEFINE_ENUM(A, B) A, SOME_ENUMS(DEFINE_ENUMS) #undef DEFINE_ENUM }; /* Now a function to return the name of an enum: */ const char *ToString(int c) { switch (c) { default: return NULL; /* Or whatever. */ #define CASE_MACRO(A, B) case A: return #b; SOME_ENUMS(CASE_MACRO) #undef CASE_MACRO } } 

下面是一个例子,如何使C代码完全不知道运行应用程序的硬件实际使用的是什么。 main.c完成设置,然后可以在任何编译器/拱形上实现自由层。 我认为把C代码抽象一下是相当整洁的,所以不需要特别说明。

在这里添加一个完整的可编译示例。

 /* free.h */ #ifndef _FREE_H_ #define _FREE_H_ #include <stdio.h> #include <string.h> typedef unsigned char ubyte; typedef void (*F_ParameterlessFunction)() ; typedef void (*F_CommandFunction)(ubyte byte) ; void F_SetupLowerLayer ( F_ParameterlessFunction initRequest, F_CommandFunction sending_command, F_CommandFunction *receiving_command); #endif /* free.c */ static F_ParameterlessFunction Init_Lower_Layer = NULL; static F_CommandFunction Send_Command = NULL; static ubyte init = 0; void recieve_value(ubyte my_input) { if(init == 0) { Init_Lower_Layer(); init = 1; } printf("Receiving 0x%02x\n",my_input); Send_Command(++my_input); } void F_SetupLowerLayer ( F_ParameterlessFunction initRequest, F_CommandFunction sending_command, F_CommandFunction *receiving_command) { Init_Lower_Layer = initRequest; Send_Command = sending_command; *receiving_command = &recieve_value; } /* main.c */ int my_hw_do_init() { printf("Doing HW init\n"); return 0; } int my_hw_do_sending(ubyte send_this) { printf("doing HW sending 0x%02x\n",send_this); return 0; } F_CommandFunction my_hw_send_to_read = NULL; int main (void) { ubyte rx = 0x40; F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read); my_hw_send_to_read(rx); getchar(); return 0; } 
 if(---------) printf("hello"); else printf("hi"); 

填空,使输出中既不会出现问候,也不会出现问题。
ans: fclose(stdout)