例如,我最近在linux内核中遇到过这个问题:
/* Force a compilation error if condition is true */ #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
所以,在你的代码中,如果你有一些必须的结构,比如8个字节的大小,可能是因为一些硬件限制,你可以这样做:
BUILD_BUG_ON((sizeof(struct mystruct) % 8) != 0);
除非struct mystruct的大小是8的倍数,否则它将不会编译,如果它是8的倍数,则根本不会生成运行时代码.
我知道的另一个技巧是"Graphics Gems"一书,它允许单个头文件在一个模块中声明和初始化变量,而在使用该模块的其他模块中,只是将它们声明为externs.
#ifdef DEFINE_MYHEADER_GLOBALS #define GLOBAL #define INIT(x, y) (x) = (y) #else #define GLOBAL extern #define INIT(x, y) #endif 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"
所以你得到一个头文件,它声明了需要它们的全局变量和函数原型的实例,以及相应的extern声明.
那么,你最喜欢的C编程技巧是什么?
C99使用匿名数组提供了一些非常酷的东西:
删除无意义的变量
{ 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" };
(或多或少,我现在没有代码检查它).
从那时起,一个创造性地使用预处理器的新世界在我眼前展开.我不再只包含标题,而是包括整个代码块(它可以提高可重用性):-p
谢谢John Carmack!的xD
我喜欢使用= {0};
初始化结构而不需要调用memset.
struct something X = {0};
这会将struct(或数组)的所有成员初始化为零(但不是任何填充字节 - 如果你需要将memset归零,则使用memset).
但是你应该知道,对于大型动态分配的结构,存在一些问题.
如果我们谈论c技巧,我最喜欢的是Duff的设备,用于循环展开!我只是在等待合适的机会来帮我实际使用它...
使用 __FILE__
和__LINE__
调试
#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};
一旦我的配偶和我重新定义返回找到一个棘手的堆栈损坏错误.
就像是:
#define return DoSomeStackCheckStuff, return
我喜欢拥有动态大小对象的"struct hack".这个网站也很好地解释了它(尽管它们引用了C99版本,你可以在其中编写"str []"作为结构的最后一个成员).你可以像这样创建一个字符串"object":
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;
在这里,我们在堆上分配了一个类型为X的结构,其大小为int(对于len),加上"hello world"的长度加1(因为str 1包含在sizeof(X)中).
当您希望在同一块中的某些可变长度数据之前有一个"标题"时,它通常很有用.
面向对象的代码用C,通过模拟类.
只需创建一个结构和一组函数,这些函数将指向该结构的指针作为第一个参数.
代替
printf("counter=%d\n",counter);
使用
#define print_dec(var) printf("%s=%d\n",#var,var); print_dec(counter);
使用愚蠢的宏技巧使记录定义更容易维护.
#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;
用于创建一个在所有模块中只读的变量,除了它声明的变量:
// 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视频编解码器的用法:
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) = { ... }
最令人高兴的是,强制每个刺激/状态检查所有代码路径都很简单.
在嵌入式系统中,我经常将ISR映射到指向这样的表并根据需要对其进行监视(在ISR之外).
另一个不错的预处理器"技巧"是使用"#"字符来打印调试表达式.例如:
#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]
我不会把它称为最喜欢的技巧,因为我从来没有使用它,但提到Duff的设备让我想起了这篇关于在C中实现Coroutines的文章.它总是让我笑一笑,但我相信它可以有一段时间有用.
#if TESTMODE == 1 debug=1; while(0); // Get attention #endif
while(0); 对程序没有任何影响,但是编译器会发出一个关于"这无所事事"的警告,这足以让我去查看有问题的行,然后看看我想引起注意的真正原因.
我是xor hacks的粉丝:
交换2个没有第三个临时指针的指针:
int * a; int * b; a ^= b; b ^= a; a ^= b;
或者我真的很喜欢只有一个指针的xor链表.(http://en.wikipedia.org/wiki/XOR_linked_list)
链表中的每个节点都是前一节点和下一节点的Xor.要遍历,可以通过以下方式找到节点的地址:
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
在您的代码位置测试语句,例如:
D(printf("Test statement\n"));
do/while有助于宏的内容扩展到多个语句.
只有在未使用编译器的'-D RELEASE'标志时才会打印该语句.
你可以,例如.将标志传递给makefile等.
不知道这在Windows中是如何工作的,但在*nix中效果很好