C99中最有用的新特性是什么?

80

C99 已存在十多年,但对它的支持一直很缓慢,所以大多数开发人员仍然使用 C89。即使今天,当我在 C 代码中遇到 C99 特性时,有时也会感到轻微的惊讶。

现在大多数主要编译器都支持 C99(MSVC 是一个明显的例外,还有一些嵌入式编译器也落后),我认为那些使用 C 的开发人员应该知道哪些 C99 特性是可用的。其中一些特性只是以前未标准化的常见特性(例如 snprintf),或者从 C++ 中熟悉(灵活的变量声明放置或单行 // 注释),但一些新功能是首次引入 C99 并且对许多程序员来说是陌生的。

你觉得 C99 中最有用的新功能是什么?

供参考,C99 标准(标记为草案,但据我所知与更新的标准相同)、新功能列表GCC C99 实现状态

每个答案一个特性,请随意留下多个答案。鼓励提供演示新功能的简短代码示例。


2
应该有一个类似的维基页面,专门记录人们对C99中不喜欢的特性! - Alok Singhal
2
请注意,在嵌入式处理器领域,C99仍然可能得不到很好的支持。 - Craig McQueen
1
@Alok 我会称之为大部分功能的支持水平;我想这取决于你如何定义它,但我认为大多数人想要使用的重要功能都得到了支持,除了一些库问题。 @Craig 很好,关于嵌入式编译器添加了免责声明。 - Brian Campbell
当然,C99最大的问题是MSVC编译器不支持下面列出的许多(任何?)最有用的功能。这使得那些必须编写在Unix和Windows上都能运行的代码的人受到了限制。 - Jonathan Leffler
哇,这真是太神奇了!作为一个偶尔使用 C 语言的程序员,我居然用到了这么多尚未完全实现的标准特性。感谢您的启示!+1 - new123456
显示剩余3条评论
17个回答

80

我已经习惯了打字。

for (int i = 0; i < n; ++i) { ... }

在C++中,如果我被迫使用非C99编译器,那么就很麻烦,因为我不得不说

int i;
for (i = 0; i < n; ++i ) { ... }

44
此外,它缩小了int变量的范围,这通常是一件好事情 ^^ - helpermethod
那也是我的选择。 - figurassa
2
@Oliver <讽刺>但是指令sub esp, 4add esp, 4被删除了!</讽刺> - Cole Tobin

74

stdint.h定义了int8_tuint8_t等类型。使用它之后,你不再需要做关于整数宽度的非可移植假设。

uint32_t truth = 0xDECAFBAD;

19
DE:AD:BE:EF:CA:FE 以来最棒的十六进制短语。 - Kevin L.
decafbad 应该是什么意思? - Pacerier
5
这只是一个任意的十六进制常数,用来举例说明。但出于幽默的目的,它拼写成“decaf bad”,意味着无咖啡因咖啡不如真正的咖啡。 - Brian Campbell
5
好的,我无法理解他们的幽默... - Pacerier

67

我认为新的初始化机制非常重要。

struct { int x, y; } a[10] = { [3] = { .y = 12, .x = 1 } };

好的-这不是一个引人注目的例子,但符号是准确的。您可以初始化数组的特定元素和结构体的特定成员。

也许一个更好的例子是这个-虽然我承认它并不是非常令人信服:

enum { Iron = 26, Aluminium = 13, Beryllium = 4, ... };

const char *element_names[] =
{
    [Iron]      = "Iron",
    [Aluminium] = "Aluminium",
    [Beryllium] = "Beryllium",
    ...
};

6
那是一个令人信服的演示。有大量的枚举表和相应的字符串表可供使用。 - u0b34a0f6ae
5
仔细观察第二个例子,你会发现初始化值是无序的,但仍然能正常工作。这不能仅通过宏定义实现。第一个例子只初始化数组的索引4,这也不能仅通过宏定义实现。 - Jonathan Leffler
2
还应该注意,多次使用相同的索引将用第二个覆盖第一个 - 例如,请参见我的问题:https://dev59.com/5HLYa4cB1Zd3GeqPYoW0 - johnny

53

支持以//开头的单行注释。


7
我所知道的所有编译器都已经支持了这个功能。现在是时候将其纳入标准中了。 - slebetman

51

可变长度数组:

int x;
scanf("%d", &x);
int a[x];
for (int i = 0; i < x; ++i)
    a[i] = i * i;
for (int i = 0; i < x; ++i)
    printf("%d\n", a[i]);

3
你真的认为VLA数组很棒吗?C11使它们成为可选项。 - Z boson
3
不要忘记应用输入过滤来防止堆栈溢出(或堆栈破坏,如果x为负数):if (x < 0) x = 0; else if (x > 1024) x = 1024; - Andrew D'Addesio

42

能够在代码块中除开头位置外的其他位置声明变量。


2
我对此并不太感兴趣。在我看来,变量不仅应该在需要时进入作用域,而且在不需要时应该从作用域中移除。几乎总是会有变量在块内停留的时间比它们应该停留的时间更长。 - supercat
5
@supercat,您更喜欢int a; int b; a = f(); b = g();而不是int a = f(); int b = g();吗?在变量声明和初始化的位置相近可大大减少错误。 - Ryan Haining
非常有价值的功能。我不分享@supercat的保留意见。通过在需要的地方(与“for(”声明相关联)定义和初始化变量,我成功地显著减少了代码的复杂性。在2010年,我不知道这一点,现在转换了数千行代码后,我对它对代码质量的积极影响感到惊讶。也通过这种方式发现了几个错误的初始化错误。 - Patrick Schlüter
错误的初始化漏洞 = 在函数级别定义并初始化为0或NULL的变量,然后在函数中的某个地方进行第一次使用,但其初始值应该是其他值。我非常惊讶我们的代码库中经常出现这种错误。 - Patrick Schlüter
@PatrickSchlüter:在像我举的例子中,让dx和dy离开作用域但保持distance不变,这样不是很有用吗? - supercat
显示剩余4条评论

36

变参宏。使用无限数量的参数可以更轻松地生成样板代码。


36

snprintf() - 严肃地说,能够进行安全格式化字符串确实非常有价值。


1
非常正确。如果没有其他人回答的话,我自己也会补充这个答案。 - Brian Campbell

30

复合字面量。逐成员设置结构已经过时了,这是上个世纪八十年代的做法 ;)

您还可以使用它们获取具有自动存储期限制的对象的指针,而无需声明不必要的变量,例如

foo(&(int){ 4 });

与其

int tmp = 4;
foo(&tmp);

它应该为4在堆栈上分配内存,对吗? - AlphaGoku

30

可变长数组成员。

6.7.2.1 结构体和联合体定义

作为一个特殊情况,如果结构体有一个以上的命名成员,则最后一个元素可以是类型为不完整数组的可变长数组成员。除了两个例外情况,这个可变长数组成员会被忽略。第一,结构体的大小必须等于另一个相同结构体中最后一个元素的偏移量,该结构体用未指定长度的数组替换了可变长数组成员。第二,当一个.(或->)运算符有一个左操作数(指向)一个具有可变长数组成员的结构体,并且右操作数命名了该成员时,它的行为就好像将该成员替换为最长的数组(具有相同元素类型),使得该数组不会使结构体比正在访问的对象更大;即使这个数组没有元素,它的偏移量也应该与可变长数组成员相同,即使这个偏移量与替换数组的偏移量不同。如果这个数组没有元素,那么它的行为就好像它有一个元素,但是如果尝试访问该元素或生成一个超出它的指针,则其行为是未定义的。

例子:

typedef struct {
  int len;
  char buf[];
} buffer;

int bufsize = 100;
buffer *b = malloc(sizeof(buffer) + sizeof(int[bufsize]));

2
终于让这个合法化了,真是太好了。我在所有的TCP/IP套接字代码中都看到过它。 - slebetman
实际上,我认为这比常见的使用buf [1]并从malloc大小中减去1的技巧更加规范。虽然这个技巧很常见,但我认为它是未定义行为(正确的做法是使用buf [MAX_SIZE]并从malloc大小中减去MAX_SIZE),因为编译器生成的索引代码可能取决于buf []的感知大小。 - supercat

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