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代码的各种技巧包括:
- 依靠编译器如何在内存中设置结构。
- 整数/浮点数字的假设。
- functionABIs的假设。
- 堆栈帧增长的方向的假设。
- 关于陈述中的执行顺序的假设
- 关于在函数参数中执行语句的顺序的假设
- 对短,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或任何其他内存分配技术之前,你必须要理智地检查参数,所以这并不是很不寻常。