我知道在所有C编译器实现背后都有一个标准,因此不应该有任何隐藏功能。尽管如此,我相信所有的C开发人员都有他们经常使用的隐藏/秘密技巧。
我知道在所有C编译器实现背后都有一个标准,因此不应该有任何隐藏功能。尽管如此,我相信所有的C开发人员都有他们经常使用的隐藏/秘密技巧。
这更像是GCC编译器的一种技巧,但你可以给编译器提供分支指示提示(在Linux内核中很常见)。
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
参见: http://kerneltrap.org/node/4705
我喜欢这个的原因是它还能为某些函数增加一些表现力。
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
这些在标准中是可选项,但肯定是一种隐藏功能,因为人们经常重新定义它们。我曾经工作过的一个代码库(现在仍然在使用)有多个重新定义,每个都有不同的标识符。大多数情况下都是用预处理器宏实现的。
#define INT16 short
#define INT32 long
等等,这让我想把头发都拔光。 就用该死的标准整数类型定义啊!
逗号运算符并不常用。它肯定可以被滥用,但它也可以非常有用。这是最常见的用法:
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
但是你可以在任何地方使用这个运算符。观察:
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
每个语句都会被评估,但表达式的值将是最后一个被评估的语句的值。将结构体初始化为零
struct mystruct a = {0};
这将把所有结构元素清零。
memset
/ calloc
会把“所有字节”都清零(即物理零),这确实并不适用于所有类型。{0}
可以保证使用适当的“逻辑零值”初始化所有内容。例如,指针保证可以得到其适当的空值,即使在给定平台上的空值是0xBAADFOOD
。 - AnT stands with Russiabool
值一样,举个例子。如果你执行bool b = false;
(或者等价的bool b = 0;
),这并不意味着在物理内存中b
会被清零(尽管在实践中通常是这样的)。 - AnT stands with Russia函数指针。您可以使用函数指针表实现快速间接线程代码解释器(FORTH)或字节码分派程序,或模拟类似面向对象的虚拟方法。
然后标准库中有一些隐藏的宝石,例如qsort(),bsearch(),strpbrk(),strcspn() [后两者用于实现strtok()替代品]。
C语言的一个问题是,有符号算术溢出是未定义的行为(UB)。因此,每当您看到这样的表达式x+y,两个都是有符号整数时,它可能会导致溢出并引起UB。
多字符常量:
int x = 'ABCD';
这将把x
设置为0x41424344
(或0x44434241
,具体取决于架构)。
编辑:此技术不具有可移植性,特别是如果您对int进行序列化。但是,它可以非常有用地创建自文档化的枚举。例如:
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)
一样小。
类似邓夫设备的交错结构:
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);
}
}
C有一个标准,但并不是所有的C编译器都完全符合(我还没有见过任何完全符合C99标准的编译器!)。
话虽如此,我喜欢的技巧是那些非显而易见且跨平台可移植的,它们依赖于C语义。它们通常是关于宏或位运算的。
例如:在不使用临时变量的情况下交换两个无符号整数:
...
a ^= b ; b ^= a; a ^=b;
...
或者"扩展C"来表示有限状态机,例如:
FSM {
STATE(x) {
...
NEXTSTATE(y);
}
STATE(y) {
...
if (x == 0)
NEXTSTATE(y);
else
NEXTSTATE(x);
}
}
可以使用以下宏来实现:
#define FSM
#define STATE(x) s_##x :
#define NEXTSTATE(x) goto s_##x
总的来说,我不喜欢那些虽然聪明但会使代码变得不必要复杂难懂的技巧(比如交换示例),而我喜欢那些可以让代码更清晰、直接表达意图的技巧(比如有限状态机示例)。
我非常喜欢在C99中添加的指定初始化程序(并且长时间支持gcc):
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
数组初始化不再依赖位置。如果您更改FOO或BAR的值,则数组初始化将自动对应其新值。