为什么在C99之前混合了声明和代码?

我最近成为一个主要教授C的大学课程的助教。C90课程标准化,主要是由于广泛的编译器支持。 对于以前的Java经验的C新手来说,一个非常混乱的概念是variables声明和代码不能混在一个块(复合语句)中的规则。

C99终于解除了这个限制,但我想知道:有人知道为什么它在那里吗? 它是否简化了variables范围分析? 它是否允许程序员指定在哪些程序执行点上堆栈应该为新variables增长?

我认为语言devise者如果完全没有任何目的,就不会增加这样的限制。

在C开头,可用的内存和CPU资源非常稀less。 所以它只需要最小的内存就可以快速编译。

因此,C语言的devise只需要一个编译速度非常快的简单编译器。 这反过来导致了“ 单通编译器 ”的概念:编译器读取源文件,并尽快将所有内容翻译为汇编代码 – 通常在读取源文件时。 例如:当编译器读取全局variables的定义时,立即发出适当的代码。

直到今天,这个特征在C中是可见的:

  • C要求“前进宣言”的一切。 一个多遍编译器可以向前看,并推导出自己在同一个文件中的函数variables的声明。
  • 这反过来使*.h文件是必要的。
  • 在编译函数时,必须尽快计算堆栈框架的布局,否则编译器必须对函数体进行多次传递。

如今没有严肃的C编译器仍然是“一次通过”,因为许多重要的优化不能一次完成。 在维基百科可以find更多一点。

标准的身体徘徊了一段时间,放松了关于function主体的“一次过”的观点。 我认为,其他的事情更重要。

就是这样,因为它一直这样做,这使得编写编译器变得容易一点,没有人真的想过以其他方式做。 最终,人们意识到让语言使用者而不是编译器编写者更容易使生活变得更加重要。

我认为语言devise者如果完全没有任何目的,就不会增加这样的限制。

不要以为语言devise者会限制语言。 像这样的限制往往是偶然的和环境的。

我想这应该是一个非优化编译器以这种方式生成高效的代码更容易:

 int a; int b; int c; ... 

尽pipe声明了3个单独的variables,但是可以立即增加堆栈指针而不优化诸如重新sorting等策略。

比较这个:

 int a; foo(); int b; bar(); int c; 

为了只增加堆栈指针一次,这需要一种优化,尽pipe不是非常先进的。

此外,作为一个风格问题,第一种方法鼓励更有纪律的编码方式(毫不奇怪,帕斯卡太强制这一点),能够看到所有的局部variables在一个地方,并最终作为一个整体进行检查。 这提供了代码和数据之间更清晰的分离。

早在C青年时代,丹尼斯·里奇(Dennis Ritchie)开始研究计算机时,计算机(例如PDP-11)的内存非常有限(例如64K字),编译器必须很小,所以不得不优化很less的东西只是。 在那个时候(我在1986-89年代用Sun4 / 110的C编码),声明寄存器variables对编译器是非常有用的。

今天的编译器要复杂得多 。 例如,最近版本的GCC(4.6)有5到10万行源代码(取决于你如何度量它),并做了大量的优化,这在第一批C编译器出现时并不存在。

而今天的处理器也非常不同 (你不能假设今天的机器就像1980年代的机器一样,但是成千上万次的机器和数千个内存和硬盘)。 今天,内存层次结构是非常重要的:caching未命中是处理器做的最多(等待RAM中的数据)。 但在1980年代,对单个机器指令的执行速度几乎与现在的标准一样快(或者比现在的标准慢)。 这在今天完全是错误的:读取你的RAM模块,你的处理器可能要等待几百纳秒,而对于L1高速caching中的数据,它可以每纳秒执行一条指令。

所以,不要把C当作一种非常接近硬件的语言:这在20世纪80年代是真实的,但是现在是错误的。

哦,但是你可以(以某种方式)混合声明和代码,但是声明新variables仅限于块的开始。 例如,以下是有效的C89代码:

 void f() { int a; do_something(); { int b = do_something_else(); } } 

要求variables声明出现在复合语句的开头并不会损害C89的performance力。 任何可以合理地使用中间块声明的事情都可以通过在声明前添加一个开放的大括号来实现,也可以加倍封闭块的大括号。 虽然这样的要求有时可能会有杂乱的源代码,并带有额外的开启和closures支撑,但这样的支撑不会只是噪声 – 它们会标记variables范围的开始和结束

考虑以下两个代码示例:

 {
   do_something_1();
   {
     int foo;
     foo = something1();
     if(foo)do_something_1(foo);
   }
   {
     int bar;
     bar = something2();
    如果(bar)do_something_2(bar);
   }
   {
     int boz;
     boz = something3();
    如果(boz)do_something_3(boz);
   }
 }

 {
   do_something_1();

   int foo;
   foo = something1();
   if(foo)do_something_1(foo);

   int bar;
   bar = something2();
  如果(bar)do_something_2(bar);

   int boz;
   boz = something3();
  如果(boz)do_something_3(boz);
 }

从运行时的angular度来看,大多数现代编译器可能不会关心在do_something3()的执行过程中foo是否在语法上在范围内,因为它可以确定它在该语句之前保留的任何值将不会被使用。 另一方面,鼓励程序员以缺乏优化编译器的方式生成次优代码的方式编写声明,这几乎不是一个吸引人的概念。

而且,处理混合variables声明的简单情况并不困难(即使是1970年代的编译器也可以这样做,如果作者想允许这样的构造的话),如果包含混合声明的块也包含gotocase标签。 C的创造者可能认为允许混合variables声明和其他声明会使标准复杂化太多而不值得。