C的隐藏function

我知道所有的C编译器实现都有一个标准,所以不应该有隐藏的特性。 尽pipe如此,我确信所有的C开发者都有隐藏/秘密的技巧。

函数指针。 您可以使用函数指针表来实现,例如快速间接线程代码解释器(FORTH)或字节码调度器,或者模拟类似OO的虚拟方法。

然后在标准库中有隐藏的gem,比如qsort(),bsearch(),strpbrk(),strcspn()[后两者对于实现strtok()replace有用)。

C的错误特征是有符号的算术溢出是未定义的行为(UB)。 因此,无论何时您看到x + y这样的expression式都是有符号整数的,它可能会溢出并导致UB。

更多GCC编译器的技巧,但你可以给编译器提供分支指示提示(在Linux内核中是常见的)

#define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) 

请参阅: http : //kerneltrap.org/node/4705

我喜欢的是它也增加了一些function的performance力。

 void foo(int arg) { if (unlikely(arg == 0)) { do_this(); return; } do_that(); ... } 
 int8_t int16_t int32_t uint8_t uint16_t uint32_t 

这些是标准中的一个可选项目,但它必须是一个隐藏的function,因为人们不断地重新定义它们。 我曾经工作过的一个代码库(现在仍然是这样)具有多重定义,所有代码都有不同的标识符。 大部分时间都是预处理器macros:

 #define INT16 short #define INT32 long 

等等。 这让我想把我的头发拉出来。 只要使用怪胎的标准整数typedefs!

逗号运算符没有被广泛使用。 它当然可以被滥用,但它也可以是非常有用的。 这种用法是最常见的一种:

 for (int i=0; i<10; i++, doSomethingElse()) { /* whatever */ } 

但你可以在任何地方使用这个操作符 注意:

 int j = (printf("Assigning variable j\n"), getValueFromSomewhere()); 

评估每个语句,但expression式的值将是最后一个语句的评估值。

初始化结构为零

 struct mystruct a = {0}; 

这将使所有的结构元素都为零。

多字符常量:

 int x = 'ABCD'; 

这将x设置为0x41424344 (或0x44434241 ,取决于体系结构)。

编辑:这种技术是不可移植的,特别是如果你序列化整数。 但是,创build自logging枚举可能非常有用。 例如

 enum state { stopped = 'STOP', running = 'RUN!', waiting = 'WAIT', }; 

这使得它更简单,如果你正在寻找一个原始的内存转储,并需要确定一个枚举的价值,而无需查找它。

我从来没有使用位域,但他们超低层次的东西听起来很酷。

 struct cat { unsigned int legs:3; // 3 bits for legs (0-4 fit in 3 bits) unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits) // ... }; cat make_cat() { cat kitty; kitty.legs = 4; kitty.lives = 9; return kitty; } 

这意味着sizeof(cat)可以和sizeof(char)一样小。


由Aaron和leppie注册 ,谢谢你们。

C有一个标准的,但不是所有的C编译器完全兼容(我还没有看到任何完全兼容的C99编译器!)。

也就是说,我所喜欢的技巧是那些在C语义上依赖的跨平台不明显和可移植的技巧。 它们通常是关于macros或位运算的。

例如:在不使用临时variables的情况下交换两个无符号整数:

 ... a ^= b ; b ^= a; a ^=b; ... 

或“扩展C”来表示有限状态机,如:

 FSM { STATE(x) { ... NEXTSTATE(y); } STATE(y) { ... if (x == 0) NEXTSTATE(y); else NEXTSTATE(x); } } 

可以用下面的macros来实现:

 #define FSM #define STATE(x) s_##x : #define NEXTSTATE(x) goto s_##x 

但总的来说,我不喜欢聪明的技巧,但是使代码不必要地复杂(作为交换的例子),我喜欢那些使代码更清晰并直接传达意图的代码(如FSM的例子) 。

交织结构像达夫的装置 :

 strncpy(to, from, count) char *to, *from; int count; { int n = (count + 7) / 8; switch (count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while (--n > 0); } } 

我非常喜欢指定的初始值设定项,在C99中添加(并在gcc中支持了很长时间):

 #define FOO 16 #define BAR 3 myStructType_t myStuff[] = { [FOO] = { foo1, foo2, foo3 }, [BAR] = { bar1, bar2, bar3 }, ... 

数组初始化不再依赖于位置。 如果更改FOO或BAR的值,则数组初始化将自动对应其新值。

C99有一些令人敬畏的任何顺序结构初始化。

 struct foo{ int x; int y; char* name; }; void main(){ struct foo f = { .y = 23, .name = "awesome", .x = -38 }; } 

匿名结构和数组是我最喜欢的一个。 (参见http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html

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

要么

 void myFunction(type* values) { while(*values) x=*values++; } myFunction((type[]){val1,val2,val3,val4,0}); 

它甚至可以用来instanciate链表…

gcc有一些我喜欢的C语言的扩展,可以在这里find。 我的一些collections夹是function属性 。 一个非常有用的例子是格式属性。 如果您定义了一个采用printf格式string的自定义函数,则可以使用此方法。 如果你启用了这个函数属性,gcc会对你的参数进行检查,以确保你的格式string和参数相匹配,并在适当的时候生成警告或错误。

 int my_printf (void *my_object, const char *my_format, ...) __attribute__ ((format (printf, 2, 3))); 

当我第一次看到“震惊”我的(隐藏)function是关于printf的。 此function允许您使用variables格式化格式说明符本身。 寻找代码,你会看到更好的:

 #include <stdio.h> int main() { int a = 3; float b = 6.412355; printf("%.*f\n",a,b); return 0; } 

*字符达到这个效果。

嗯…我认为C语言的优点之一就是它的可移植性和标准性,所以每当我在实现中我find一些“隐藏的技巧”时,我尽量不使用它,因为我试图让我的C代码作为标准和可移植的。

编译时断言, 这里已经讨论过了 。

 //--- size of static_assertion array is negative if condition is not met #define STATIC_ASSERT(condition) \ typedef struct { \ char static_assertion[condition ? 1 : -1]; \ } static_assertion_t //--- ensure structure fits in STATIC_ASSERT(sizeof(mystruct_t) <= 4096); 

常量string连接

我很惊讶没有看到答案,因为我知道所有的编译器支持它,但很多程序员似乎忽略它。 有时它非常方便,不仅在编写macros时。

在我当前的代码中使用的情况是:我在configuration文件中有一个#define PATH "/some/path/" (实际上它是由makefile设置的)。 现在我想构build包含文件名的完整path来打开资源。 它只是去:

 fd = open(PATH "/file", flags); 

而不是可怕的,但很常见:

 char buffer[256]; snprintf(buffer, 256, "%s/file", PATH); fd = open(buffer, flags); 

请注意,常见的可怕解决scheme是:

  • 三倍的时间
  • 更不容易阅读
  • 慢得多
  • 在它设置为任意的缓冲区大小限制(但是你将不得不使用更长的代码,以避免没有常量stringcontatenation)function更强大。
  • 使用更多的堆栈空间

好吧,我从来没有用过它,我不知道我是否会向任何人推荐它,但是我觉得这个问题是不完整的,没有提到西蒙·塔特姆的常规把戏。

初始化数组或枚举时,可以在初始化程序列表中的最后一项之后加逗号。 例如:

 int x[] = { 1, 2, 3, }; enum foo { bar, baz, boom, }; 

这样做是为了如果你自动生成代码,你不必担心消除最后的逗号。

结构分配很酷。 很多人似乎并没有意识到结构也是价值的,可以分配,不需要使用memcpy() ,当一个简单的任务做的伎俩。

例如,考虑一些虚构的2Dgraphics库,它可能定义一个types来表示(整数)屏幕坐标:

 typedef struct { int x; int y; } Point; 

现在,你做的事情可能看起来是“错误的”,比如写一个函数,它创build一个从函数参数初始化的点,并返回它,如下所示:

 Point point_new(int x, int y) { Point p; px = x; py = y; return p; } 

这是安全的,只要(当然)返回值是通过使用struct赋值来复制的:

 Point origin; origin = point_new(0, 0); 

通过这种方式,你可以编写相当干净和面向对象的代码,所有代码都是纯C标准的。

奇怪的vector索引:

 int v[100]; int index = 10; /* v[index] it's the same thing as index[v] */ 

C编译器实现了几个标准之一。 但是,有一个标准并不意味着语言的所有方面都被定义了。 例如, Duff的设备是最受欢迎的“隐藏”function,现代编译器具有特殊用途识别代码,以确保优化技术不会破坏这种常用模式的预期效果。

一般情况下,当你在编译器使用的任何C标准的razor边缘上运行隐藏function或语言技巧时,都是不鼓励的。 许多这样的技巧不能从一个编译器工作到另一个编译器,并且通常这些types的特征将从给定制造商的编译器套件的一个版本失败到另一个版本。

破坏C代码的各种技巧包括:

  1. 依靠编译器如何在内存中设置结构。
  2. 整数/浮点数字的假设。
  3. functionABIs的假设。
  4. 堆栈帧增长的方向的假设。
  5. 关于陈述中的执行顺序的假设
  6. 关于在函数参数中执行语句的顺序的假设
  7. 对短,int,long,float和doubletypes的位大小或精度的假设。

当程序员对大多数C标准中所有被指定为“编译器依赖”行为的执行模型进行假设时,就会出现其他问题和问题。

当使用sscanf时,你可以使用%n找出你应该继续阅读的地方:

 sscanf ( string, "%d%n", &number, &length ); string += length; 

显然,你不能添加另一个答案,所以我会在这里包括第二个答案,你可以使用“&&”和“||” 作为条件:

 #include <stdio.h> #include <stdlib.h> int main() { 1 || puts("Hello\n"); 0 || puts("Hi\n"); 1 && puts("ROFL\n"); 0 && puts("LOL\n"); exit( 0 ); } 

这段代码将输出:

 你好
 ROFL 

使用INT(3)在代码中设置断点是我所有的时间最喜欢的

C中我最喜欢的“隐藏”function是在printf中使用%n来回写堆栈。 通常情况下,printf基于格式stringpopup堆栈中的参数值,但是%n可以将其写回。

在这里查看3.4.2节。 可能会导致很多令人讨厌的漏洞。

使用枚举进行编译时的假设检查:愚蠢的例子,但是对于具有编译时可configuration常量的库来说可能非常有用。

 #define D 1 #define DD 2 enum CompileTimeCheck { MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)), MAKE_SURE_DD_IS_POW2 = 1/((((DD) - 1) & (DD)) == 0) }; 

GCC(c)有一些你可以启用的有趣function,比如嵌套的函数声明,以及?:运算符的?:bforms,它返回一个如果a不是假的。

我发现最近0位字段。

 struct { int a:3; int b:2; int :0; int c:4; int d:3; }; 

这将给出一个布局

 000aaabb 0ccccddd 

而不是没有:0;

 0000aaab bccccddd 

0宽度字段表示应在下一个primefaces实体( char )上设置以下位域:

C99风格的variables参数macros,又名

 #define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \ __VAR_ARGS__) 

这将使用像

 ERR(errCantOpen, "File %s cannot be opened", filename); 

在这里,我也使用了string运算符和string常量concatenation,我真的很喜欢其他function。

在某些情况下,variables大小的自动variables也是有用的。 这些都是在nC99中join的,并且已经在gcc中得到了很长时间的支持。

 void foo(uint32_t extraPadding) { uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding]; 

最后在堆栈中放置一个缓冲区,为固定大小的协议头和可变大小数据提供空间。 你可以用alloca()获得同样的效果,但是这个语法更加紧凑。

在调用这个例程之前,你必须确保extraPadding是一个合理的值,否则你最终会吹掉堆栈。 在调用malloc或任何其他内存分配技术之前,你必须要理智地检查参数,所以这并不是很不寻常。