C语言的隐藏特性

141

我知道在所有C编译器实现背后都有一个标准,因此不应该有任何隐藏功能。尽管如此,我相信所有的C开发人员都有他们经常使用的隐藏/秘密技巧。


如果你/有人能够编辑这个“问题”,指出最好的隐藏功能选择,就像在C#和Perl版本的问题中那样,那将是很棒的。 - Donal Fellows
56个回答

5

5

在GCC中使用Lambda表达式(例如匿名函数):

#define lambda(return_type, function_body) \
    ({ return_type fn function_body fn })

这可以用作:

lambda (int, (int x, int y) { return x > y; })(1, 2)

这可以扩展为:

({ int fn (int x, int y) { return x > y } fn; })(1, 2)

5

我喜欢你能够创建可变大小的结构:

typedef struct {
    unsigned int size;
    char buffer[1];
} tSizedBuffer;

tSizedBuffer *buff = (tSizedBuffer*)(malloc(sizeof(tSizedBuffer) + 99));

// can now refer to buff->buffer[0..99].

还有一个在ANSI C中现在已经存在的宏offsetof,但第一次看到时它就像是一种魔法。它基本上使用取地址运算符(&)将空指针重新解释为一个结构变量。


1
这不符合标准;C99有“灵活数组成员”,可以实现相同的结果并且符合标准。(从声明中删除1以使用灵活数组成员。) - Jonathan Leffler

4

清除输入缓冲区时不能使用 fflush(stdin)。正确的方法如下:scanf("%*[^\n]%*c"),这将丢弃输入缓冲区中的所有内容。


@Tomas Senart。你有相关的参考资料吗?我完全同意你不能使用fflush(stdin)来清空缓冲区,这只在Windows C编译器上有效(http://msdn.microsoft.com/en-us/library/9yky46tz%28v=VS.100%29.aspx),但在gcc中无效。 - Cacho Santa

3
早期的gcc版本在源代码中遇到“#pragma”时会尝试运行游戏。更多信息请参见这里

#pragma GCC毒瘤标识符该指令禁止程序中使用标识符。毒瘤标识符不能进行#ifdef或#undef操作,任何尝试使用它们的行为都将产生错误。标识符是由空格分隔的列表。 - squadette

3

在进行了15年以上的C编程之后,我才发现这一点:

struct SomeStruct
{
   unsigned a : 5;
   unsigned b : 1;
   unsigned c : 7;
};

位域!冒号后面的数字是成员所需的位数,成员被打包到指定类型中,因此如果unsigned是16位,则上述内容将如下所示:

xxxc cccc ccba aaaa

Skizz


3

有一次我在代码中看到了这个,然后问它是做什么的:


hexDigit = "0123456789abcdef"[someNybble];

另一个受欢迎的选择是:


unsigned char bar[100];
unsigned char *foo = bar;
unsigned char blah = 42[foo];

第一个太简单了。我想你的意思是someNybble["0123456789abcdef"]。第二个在添加*之前无法编译。 - Windows programmer
我认为第一个是正确的:它将范围在0-15之间的整数someNybble转换为其十六进制等效形式。 - Adam Liss
第一种形式和第二种形式都是正确的,但是展示的那个并不是真正棘手的,只是可能不太常见,但很容易理解。 - Blaisorblade

3

使用不寻常的类型转换进行类型转换。虽然不是隐藏功能,但它相当棘手。

示例:

如果您想知道编译器如何存储浮点数,请尝试以下操作:

uint32_t Int;
float flt = 10.5; // say

Int = *(uint32_t *)&flt;

printf ("Float 10.5 is stored internally as %8X\n", Int);

或者

float flt = 10.5; // say

printf ("Float 10.5 is stored internally as %8X\n", *(uint32_t *)&flt);

注意巧妙使用类型转换。将变量的地址(这里是&flt)转换为所需类型(这里是(uint32_t *)),并提取其内容(应用“*”)。

同样地,此方法也适用于表达式的另一侧:

*(float *)&Int = flt;

也可以使用union来实现:

typedef union
{
  uint32_t Int;
  float    flt;

} FloatInt_type;

3
这属于“常见用法,我建议避免使用”。类型别名和优化不能很好地配合。为了读者和编译器的清晰度,请改用联合类型。 - ephemient
准确来说,“不相容”意味着“这段代码可能会被错误编译”,因为在C语言中,这是未定义的行为。 - Blaisorblade

2

intptr_t用于声明指针类型的变量。它是C99特定的类型,需要在stdint.h中声明。


2

Steve Webb提到了__LINE____FILE__宏。这让我想起了在我的上一份工作中,我曾经利用它们来进行内存日志记录。

当时我正在开发一个设备,该设备没有可用的端口将日志信息从设备传递到用于调试的PC上。可以使用断点来停止程序并使用调试器了解程序状态,但是系统跟踪上没有任何信息。

由于所有调试日志调用实际上都是单个全局宏,因此我们更改了该宏以将文件名和行号转储到全局数组中。该数组包含一系列文件名和行号,显示哪些调试调用被调用,从而给出执行跟踪的公平想法(虽然不是实际的日志消息)。可以通过调试器暂停执行,将这些字节转储到本地文件,然后使用脚本将此信息映射到代码库。这是可能的,因为我们有严格的编码指南,所以我们可以在一个文件中对日志记录机制进行更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接