你最喜欢的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
的概念。 基本上,你不需要为列表中的每个结构指定next
和last
字段。 而是,您将列表结构标题附加到实际链接的项目。
看一下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)