开源项目中的C99混合声明和代码?

7

为什么像Linux内核GNOME这样的开源C项目仍然不使用C99混合声明和代码

我真的很喜欢混合声明和代码,因为它使代码更易读,并通过将变量的作用域限制在最窄范围内来防止难以发现的错误。谷歌推荐C++使用此功能。

例如,Linux 需要至少GCC 3.2,而GCC 3.1 支持C99混合声明和代码。


7
我非常不喜欢混合声明,因为它们会使代码变得难以阅读并导致难以发现的错误。 - Carl Norum
1
Justin,我对你的修改意见完全不同意。我讨厌看到一个可以适用于多种语言的标题(用大号粗体字),却必须在屏幕的其他地方寻找用低对比度、小号字体写的标签,才能知道我是否有关于问题的发言。只通过标签来消除歧义的标题是对可用性的噩梦。而且就可用性而言,标签通常是添加信息而非取代它。 - Pascal Cuoq
4
@Carl,@Eduardo 我认为你们都夸大了对方偏好风格可能引起的错误。函数作用域变量会导致代码腐败,因为代码被移除后,变量变得无用,而块作用域可能会导致变量遮蔽,如果不小心命名,就会出现这种情况。虽然一组良好的编译器警告可以捕捉到这两个问题,但狭窄的作用域仍然更优越,因为它使得更容易发现傻瓜式的数据使用模式…这反过来又导致更好、更易懂的代码。 - Dan Olson
1
@Carl:我认为这是一种概括:有时,混合声明可以使代码更易读;一个经常出现的例子是:在函数体内首先检查函数参数的前提条件,因为你知道 - 这是一个前置条件;仅为此添加另一个块(导致额外的缩进级别)似乎有些过度设计。 - Christoph
@Eduardo,符合ANSI C89标准的声明并不会阻止变量作用域的缩小,它们只是防止了声明与语句混合使用。在使用gcc -std=c89 -pedantic进行实际实验几个小时后,你就会发现这一点。为什么不先尝试一下呢?基于你明显不理解的东西来评价代码可读性是不明智的。 - user3137850
显示剩余2条评论
7个回答

4

您无需混合声明和代码来限制作用域。您可以这样做:

{
  int c;
  c = 1;
  {
    int d = c + 1;
  }
}

在C89中,就为什么这些项目没有使用混合声明(假设这是真的),最有可能的情况是“不修理坏事”。

3

虽然这是一个老问题,但我认为惯性是这些项目仍然使用 ANSI C 声明规则的原因。

不过还有其他一些可能性,从合理到荒谬:

  • 可移植性。许多开源项目基于 ANSI C 的严谨规则来编写软件是最具可移植性的做法。

  • 历史。许多这样的项目在 C99 规范之前就存在了,作者可能更喜欢一致的编码风格。

  • 无知。提交代码的程序员比 C99 更早,不知道混合声明和代码的好处。(或者:开发者完全意识到可能出现的权衡,并决定混合声明和语句不值得努力。我非常不同意,但很少有两个程序员会对任何东西达成一致的见解。)

  • FUD(恐惧、不确定性和怀疑)。程序员认为混合声明和代码是“C++主义”,因此不喜欢它。


2

没有理由为了没有性能提升的表面变化而重写Linux内核。

如果代码基础已经运作正常,为何要因为表面原因而做出改变呢?


1
不需要重写任何东西。但是建议在新代码中缩小变量范围。 - Eduardo
代码库中的一致性是非常重要的。 - Alan
1
毕竟,愚蠢的一致性是小心灵的妖怪。而且没有人的思维比考虑编码标准时更狭窄。;-) - Steve Jessop
@Eduardo 我没有读过整个Linux源代码,但我也用ANSI C编写。在我的代码中,我的函数很小,变量的作用域不能更窄。对我来说,这个特性解决了我从未遇到过的问题。 - user26742873

1

我不记得内核代码风格指南中有任何禁止这样做的规定。然而,它确实说函数应该尽可能小,并且只做一件事情。这就解释了为什么声明和代码混合在一起是罕见的。

在一个小函数中,在作用域开始时声明变量就像一种引子,告诉你接下来会发生什么。在这种情况下,变量声明的移动是如此有限,以至于它可能没有任何影响,或者通过将吆喝者推到人群中来隐藏一些关于功能的信息。有一个原因,为什么国王进入房间之前要先宣布他的到来。

另一方面,必须混合变量和代码才能使其可读的函数可能太大了。这是一个迹象(连同太多嵌套块、内联注释和其他东西),表明函数的某些部分需要抽象成单独的函数(并声明为static,以便优化器可以将它们内联)。

将声明放在函数开头的另一个原因是:如果您需要重新排序代码中语句的执行顺序,您可能会移动变量的作用域而没有意识到,因为在代码中间声明变量的作用域不明显(除非您使用块来显示作用域)。这很容易修复,所以只是一种烦恼,但新代码经常经历这种转换,烦恼可能会累积。

还有一个原因:您可能会想要声明一个变量来接收函数的错误返回代码,如下所示:

void_func();
int ret = func_may_fail();
if (ret) { handle_fail(ret) }

这是一个非常合理的做法。但是:

void_func();
int ret = func_may_fail();
if (ret) { handle_fail(ret) }
....
int ret = another_func_may_fail();
if (ret) { handle_other_fail(ret); }

糟糕!ret被定义了两次。你说:“那又怎样?删除第二个声明。”但这会使代码不对称,最终导致更多的重构限制。

当然,我自己也会混合声明和代码;没有理由要死守某种规则(否则你的业力可能会压垮你的教条:-)。但你应该知道相关的问题。


1

这样做没有好处。在函数开头声明所有变量(类似Pascal)更加清晰,在C89中,您也可以在每个作用域的开头声明变量(例如循环内部),这既实用又简洁。


0

也许这并不是必需的,也许分离是好的?我用C++实现,它也有这个功能。


0

没有理由改变像这样的代码,而且C99仍然没有得到广泛支持。这主要是关于可移植性的问题。


3
“C99仍未被广泛支持” - 这在Linux内核中是一个问题吗?例如,对于一些平台,Linux+GCC会使用C89编译器进行引导吗? - Steve Jessop
可能不适用于Linux内核,因为它与GCC及其汇编器紧密相关,但对于许多项目来说,这是一个要求。Linux太大了,无法为无用的原因重构所有代码以使其像这样运行。 - alternative

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