C语言的隐藏特性

141

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


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

2

并不是真正的隐藏功能,但第一次看到像这样的东西时,它让我感觉像巫术:


void callback(const char *msg, void *data)
{
    // do something with msg, e.g.
    printf("%s\n", msg);

    return;
    data = NULL;
}

这个构造的原因是,如果你使用-Wextra编译并且没有"data = NULL;"这行代码,gcc会报一个有关未使用参数的警告。但是加上这行无用的代码就不会收到警告。
编辑:我知道有其他(更好的)方法来避免这些警告。但是第一次看到时,这让我感到很奇怪。

1
不,签名没有改变。你可以这样做: void callback(const char msg, void * / data*/ )或者这样做: void callback(const char *msg, void *) - Greg Whitfield
1
使用gcc,您可以向参数添加未使用的属性:void callback(const char *msg, void *data attribute((unused))) - DGentry
4
与使用非可移植的__attribute__语法相反,您可以直接在函数中加入以下语句来达到同样的效果:(void)data;我通常会将该语句放在本地变量之后(因为它们必须在 C89 中先声明)。我也倾向于创建一个宏,如下所示:#define UNUSED(x) (void)x这样我可以直接写:UNUSED(data)。 - Evan Teran
1
你可以在任何地方使用“(void)data”,直到“return”。(似乎Evan已经说过了) - akauppi
你不也可以只使用 #unused data 吗? - Brian Postow
显示剩余5条评论

2

将变量与字面值进行比较时,最好将字面值放在==运算符的左侧,以确保当您错误地使用赋值运算符时编译器会发出错误提示。

if (0 == count) {
    ...
}

一开始看起来可能有些奇怪,但它可以避免一些麻烦(比如如果你不小心打成if (count = 0))。


这是一种被某些人宗教般地使用的老技巧,但我相信许多编译器在看到 if (count = 0) 时会生成警告,使其有些多余。 - Mark Ransom

1

摘要:

在这个页面上,您将找到一些有趣的C编程问题/谜题列表。这些程序是我从朋友的电子邮件转发中收到的,也有一些是我在书籍中读到的,还有一些是我从互联网上获取的,以及一些是我在C编程经验中获得的。

http://www.gowrikumar.com/c/index.html


1

寄存器变量

我曾经使用register关键字声明一些变量以加快速度。这会提示C编译器将CPU寄存器用作本地存储。现代C编译器自动执行此操作,因此这很可能不再必要。


3
更重要的是,C编译器比你更清楚哪些变量在寄存器中受益最大。现代大多数编译器已经足够聪明,可以完全忽略register关键字,但如果它们真的关注它,那么可能会使您的代码变慢。 - Mark Baker
2
我非常确定有些编译器不允许您获取使用register声明的变量的地址。因此,为了保持您的意图清晰,这是有用的。 - Zan Lynx
实际上,当你在特定的场景下进行代码优化和混合汇编时,这个关键字确实有其优点,并且可以极大地提高速度。当然,这些都是边角情况(我在EE上发布的一篇文章展示了何时何地使用它是有用的,但那是很久以前的事了)。 - Abel
考虑到微控制器与汇编输出的组合,即使在一些奇怪的情况下,这仍可能是有用的。 - XTL

1

函数指针的大小不是标准的。至少在K&R书中没有。尽管它谈论了其他类型指针的大小,但(我认为)sizeof函数指针是未定义行为。

sizeof也是一个编译时运算符,我看到很多人在在线论坛上问sizeof是函数还是运算符。

我见过的一个错误如下(一个简化的例子):

int j;
int i;
j = sizeof(i++)

i 的增量不会被执行,因为 sizeof 在编译时被计算。程序员打算在一个语句中同时修改 i 的增量和 sizeof 的计算。

C 中的运算符优先级控制的是结合顺序而不是计算顺序。例如,如果你有三个函数 fgh,每个函数都返回一个 int,并且有一个表达式如下:

f() + g() * h()

C标准没有规定这些函数的评估顺序。在将gh的结果相乘之前,将添加f的结果。如果这些函数共享状态并且计算依赖于这些函数的评估顺序,则可能会导致错误。这可能会导致可移植性问题。


1

假设你有一个成员都是同一类型的结构体:

struct Point {
    float x;
    float y;
    float z;
};

您可以将其实例转换为浮点指针并使用数组索引:

Point a;
int sum = 0, i = 0;
for( ; i < 3; i++)
    sum += ((float*)a)[i];

虽然很基础,但在编写简洁代码时非常有用。


11
你确定这是可移植的吗?我认为C标准除了第一个元素偏移量必须为0之外,对结构体对齐并没有任何保证。元素之间可能存在间隙。也就是说,sizeof(Point)不保证等于sizeof(float)*3。 - jmtd
1
@jmtd,没错。实际上,它的“可移植性”恰好足以让你陷入麻烦。除第一个成员外的任何成员的偏移量都是实现定义行为,并且不需要具有与该类型的数组相同的有效打包。实际上,它很可能具有与数组相同的打包方式,因此此代码将在移植到下一个平台之前正常工作,然后会出现神秘的故障。当常见的MD5实现移植到64位时也发生了类似的事情:它编译并运行,但得到了不同的答案。 - RBerteig

1

这里有三个在gcc中不错的:

__FILE__ 
__FUNCTION__
__LINE__

1
__FILE__和__LINE__是标准的;C99引入了__func__。 - philant

0

在 switch 语句中使用 while(0),这样你就可以像使用 break 语句一样使用 continue 语句,怎么样?

void sw(int s)
{
    switch (s) while (0) {
    case 0:
        printf("zero\n");
        continue;
    case 1:
        printf("one\n");
        continue;
    default:
        printf("something else\n");
        continue;
    }
}

0

变长结构体,在常见的解析器库等地方看到。

struct foo
{
  int a;
  int b;
  char b[1]; // 使用 [0] 不再正确
             // 必须放在最后
};
char *str = "abcdef"; int len = strlen(str); struct foo *bar = malloc(sizeof(foo) + len);
strcpy(bar.b, str); // 尝试阻止我吧!

在 C99 中,正确的声明方式是: char b []; 这种方式有一个优点,你不需要减去结构体体积中 1*sizeof b[0] 的大小。 - Patrick Schlüter
1
访问超出声明大小的b[]是否未定义行为,而不管是否为其分配了空间?我认为使用char b [MAX_ARRAY_SIZE]然后从分配中减去MAX_ARRAY_SIZE会更清晰。更好的方法是如果首先允许零大小数组,并且编译器要求将它们视为指向数组开始的指针,但没有大小限制。 - supercat

0

我刚读了这篇文章。它涉及到一些 C 语言和其他几种编程语言的“隐藏特性”。


哦,我的天啊!它们都是stackoverflow的贡献,很抱歉(我有点新来这里,没有注意到有一个隐藏功能部分)... 无论如何,它可能作为这些主题的参考和快速指南。 - Rigo Vides

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