[英]Why was mixing declarations and code forbidden up until C99?
我最近成为一门主要教授 C 的大学课程的助教。该课程在 C90 上标准化,主要是由于广泛的编译器支持。 对于具有 Java 经验的 C 新手来说,非常令人困惑的概念之一是变量声明和代码不能在块(复合语句)中混合的规则。
C99 终于解除了这个限制,但我想知道:有人知道它为什么会存在吗? 它是否简化了可变范围分析? 它是否允许程序员指定堆栈应该在程序执行的哪个点为新变量增长?
我认为如果它完全没有任何目的,语言设计者就不会添加这样的限制。
在 C 的最初阶段,可用内存和 CPU 资源非常稀缺。 所以它必须以最少的内存需求快速编译。
因此,C 语言被设计为只需要一个非常简单的编译器即可快速编译。 这反过来又导致了“单程编译器”的概念:编译器读取源文件并尽快将所有内容转换为汇编代码——通常是在读取源文件时。 例如:当编译器读取全局变量的定义时,会立即发出相应的代码。
直到今天,这个特性在 C 中仍然可见:
*.h
文件成为必需。现在没有一个严肃的 C 编译器仍然是“single pass”,因为许多重要的优化不能在一次 pass 中完成。 可以在Wikipedia 中找到更多内容。
标准体徘徊了相当长的一段时间,以放松有关功能体的“单通”点。 我想,其他事情更重要。
之所以这样,是因为它一直都是这样做的,它使编写编译器变得更容易一些,而且没有人真正想过以其他方式这样做。 随着时间的推移,人们意识到更重要的是让语言用户的生活更轻松,而不是编译器编写者。
我认为如果它完全没有任何目的,语言设计者就不会添加这样的限制。
不要假设语言设计者开始限制语言。 像这样的限制通常是偶然和环境产生的。
我想非优化编译器以这种方式生成高效代码应该更容易:
int a;
int b;
int c;
...
虽然声明了 3 个独立的变量,但堆栈指针可以一次递增,无需优化策略,例如重新排序等。
将此与:
int a;
foo();
int b;
bar();
int c;
只增加一次堆栈指针,这需要一种优化,虽然不是非常高级的优化。
此外,作为一个文体问题,第一种方法通过能够在一个地方看到所有局部变量并最终将它们作为一个整体一起检查来鼓励更规范的编码方式(难怪 Pascal 也强制执行此操作)。 这在代码和数据之间提供了更清晰的分离。
要求变量声明出现在复合语句的开头不会影响 C89 的表达能力。 使用中间块声明可以合法地做的任何事情都可以通过在声明之前添加一个左大括号并将封闭块的右大括号加倍来完成。 of variables' scopes.虽然这样的要求有时可能会用额外的左括号和右括号使源代码混乱,但这样的括号不会只是噪音——它们会标记变量范围的开始。
考虑以下两个代码示例:
{ 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 年的编译器也可以做到,如果作者想要允许这样的构造),如果包含混合声明的块也包含任何goto
,事情就会变得更加复杂或case
标签。 C 的创建者可能认为允许变量声明和其他语句的混合会使标准过于复杂,不值得从中受益。
回到 C 青年时代,Dennis Ritchie 研究它时,计算机(例如PDP-11 )的内存非常有限(例如 64K 字),并且编译器必须很小,因此它必须优化的东西很少,并且很简单。 在那个时候(我在 1986-89 时代的Sun-4 / 110上用 C 编码),声明寄存器变量对编译器非常有用。
今天的编译器要复杂得多。 例如,最新版本的 GCC (4.6) 有超过 5 或 1000 万行源代码(取决于您如何衡量它),并且进行了大量优化,而这在第一个 C 编译器出现时是不存在的。
而且今天的处理器也大不相同(你不能假设今天的机器就像 1980 年代的机器,但速度快了数千倍,RAM 和磁盘也多出数千倍)。 今天,内存层次结构非常重要:缓存未命中是处理器最常做的事情(等待来自 RAM 的数据)。 但是在 1980 年代,对内存的访问几乎与执行单个机器指令一样快(或按照当前标准慢)。 今天这是完全错误的:要读取您的 RAM 模块,您的处理器可能需要等待数百纳秒,而对于 L1 缓存中的数据,它每纳秒可以执行超过一条指令。
所以不要认为 C 是一种非常接近硬件的语言:这在 1980 年代是正确的,但在今天是错误的。
哦,但是您可以(在某种程度上)混合声明和代码,但是声明新变量仅限于块的开头。 例如,以下是有效的C89代码:
void f()
{
int a;
do_something();
{
int b = do_something_else();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.