为什么在C99之前混合声明和代码被禁止?

时间:2011-10-22 12:13:41

标签: c c99 c89

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

这个限制终于被C99取消了,但我想知道:有人知道为什么它首先在那里吗?它是否简化了可变范围分析?它是否允许程序员指定堆栈应该为新变量增加哪些程序执行点?

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

6 个答案:

答案 0 :(得分:52)

在C的最开始,可用的内存和CPU资源确实稀缺。所以它必须以最小的内存要求快速编译。

因此,C语言被设计为只需要一个非常简单的编译器,它可以快速编译。这反过来导致“single pass compiler”概念:编译器读取源文件并尽快将所有内容转换为汇编代码 - 通常在读取源文件时。例如:当编译器读取全局变量的定义时,会立即发出相应的代码。

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

  • C需要所有和所有内容的“前向声明”。多传递编译器可以向前看并在自己的同一文件中推断出函数变量的声明。
  • 这反过来又需要*.h个文件。
  • 编译函数时,必须尽快计算堆栈帧的布局 - 否则编译器必须对函数体进行多次传递。

现在没有严肃的C编译器仍然是“单通”,因为许多重要的优化不能在一次通过中完成。在Wikipedia中可以找到更多内容。

标准身体徘徊了相当长的一段时间,以放松关于功能体的“单程”点。我认为,其他事情更重要。

答案 1 :(得分:8)

就是这样,因为它一直以这种方式完成,它使编写编译器变得更容易,并且没有人真正想过以任何其他方式进行编写。随着时间的推移,人们意识到,让语言用户而不是编译器编写者更容易让生活变得更加重要。

  

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

不要假设语言设计者开始限制语言。这种限制通常是偶然和环境造成的。

答案 2 :(得分:4)

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

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

虽然声明了3个单独的变量,但是堆栈指针可以立即递增,而不会优化重新排序等策略。

将其与:

进行比较
int a;
foo();
int b;
bar();
int c;

要将堆栈指针递增一次,这需要一种优化,尽管不是非常高级的。

此外,作为一个风格问题,第一种方法鼓励采用更有纪律的编码方式(难怪Pascal也强制执行此操作),因为能够在一个地方看到所有局部变量并最终将它们作为一个整体进行检查。这提供了代码和数据之间更清晰的分离。

答案 3 :(得分:2)

回到C青年时代,当Dennis Ritchie开展工作时,计算机(例如PDP-11)的内存非常有限(例如64K字),编译器必须很小,所以必须优化很少事情非常简单。那时(我在1986-89年代的Sun4 / 110编码C中),声明寄存器变量对编译器非常有用。

今天的编译器要复杂得多。例如,最新版本的GCC(4.6)有超过5或1000万行源代码(取决于你如何测量它),并且在第一批C编译器出现时进行了大量的优化。

今天的处理器也非常不同(你不能认为今天的机器就像20世纪80年代的机器,但成千上万的时间紧张,数以千计的RAM和磁盘)。今天,内存层次结构非常重要:缓存未命中是处理器最常用的(等待来自RAM的数据)。但是在20世纪80年代,对内存的访问几乎与执行单个机器指令一样快(或者按当前标准一样慢)。今天完全错误:要读取RAM模块,处理器可能需要等待几百纳秒,而对于L1缓存中的数据,它可以每纳秒执行一条指令。

所以不要认为C语言非常接近硬件:这在1980年代是正确的,但今天却是假的。

答案 4 :(得分:2)

哦,但你可以(在某种程度上)混合声明和代码,但声明新变量仅限于块的开头。例如,以下是有效的C89代码:

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

答案 5 :(得分:0)

要求变量声明出现在复合语句的开头并不会损害C89的表达能力。使用中间块声明可以合法地做的任何事情也可以通过在声明之前添加一个开括号并且使封闭块的右括号加倍来完成。虽然这样的要求有时可能会使具有额外开启和关闭括号的源代码混乱,但这样的括号不仅仅是噪声 - 它们将标记变量范围的开始和结束

考虑以下两个代码示例:

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

{
  do_something_1();

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

  int bar;
  bar = something2();
  if (bar) do_something_2(bar);

  int boz;
  boz = something3();
  if (boz) do_something_3(boz);
}

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

此外,虽然处理涉及混合变量声明的简单案例并不困难(即使1970年的编译器可以完成它,如果作者想要允许这样的构造),如果包含混合声明的块也会变得更复杂包含任何gotocase标签。 C的创建者可能认为允许混合变量声明和其他语句会使标准过于复杂而不值得获益。