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

Rav*_*mio 40 c c99 c89

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

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

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

A.H*_*.H. 52

在C的最开始,可用内存和CPU资源非常稀少.所以它必须以最小的内存要求快速编译.

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

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

  • C需要所有和所有内容的"前向声明".多传递编译器可以向前看并在自己的同一文件中推导出函数变量的声明.
  • 这反过来使*.h文件成为必要.
  • 当编译一个函数,栈帧的布局,必须尽快计算 - 否则编译器有超过函数体中做几道.

现在没有严肃的C编译器仍然是"单程",因为许多重要的优化不能在一次传递中完成.在维基百科中可以找到更多.

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

  • "在编译函数时,必须尽快计算堆栈帧的布局" - 尽管限制是变量是在*块*的开头声明的,不一定是*函数的开始*.因此,单通道C89编译器必须在进入时移动堆栈指针并退出到每个块以利用该限制.在这种情况下,我认为每次在代码中间声明变量时都可以移动堆栈指针(实际上,在该点启动一个额外的范围)并保持单遍. (6认同)
  • 即使在这个解释之后,我也不知道在块的开头定义变量的优点是什么.由于您可以在初始化程序中放置任意表达式,因此编译器仍然需要能够处理与声明混合的任意代码. (5认同)
  • @AH:好吧,即使对于 C89,在这些约束下的单遍编译器也是不可能的。很明显,在 1989 年,编译器技术和能力已经存在,可以记住中间结果存储在堆栈上的位置,而不是只能压入和弹出它们。鉴于 C89 打破了任何不能做到这一点的旧技术编译器,我真的不明白他们为什么保留限制,除了因为这是每个人都习惯的风格。 (2认同)
  • 我已经实现了一个单遍 C 到汇编编译器,其中可以在每个块中的语句之后进行声明。它是如何完成的:跟踪局部变量分配直到当前编译的函数结束并计算变量的累积大小。显然,您在函数开始时需要这个大小,但当时没有。但没有什么可以阻止你以这种方式生成代码: `goto L1` + `L2: 函数体代码` + `return` + `L1: stack_pointer -= size_of_locals` + `goto L2`。 (2认同)

Dav*_*nan 8

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

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

不要以为语言设计者开始限制语言.像这样的限制通常是出于机会和环境.


Bla*_*iev 5

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

int a;
int b;
int c;
...
Run Code Online (Sandbox Code Playgroud)

虽然声明了 3 个独立的变量,但堆栈指针可以一次递增,无需优化策略,例如重新排序等。

将此与:

int a;
foo();
int b;
bar();
int c;
Run Code Online (Sandbox Code Playgroud)

只增加一次堆栈指针,这需要一种优化,虽然不是非常高级的优化。

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

  • 实际上,我认为在尽可能接近第一次使用时声明 vars 在风格上更优越,以最小化它们的范围。但这是一个不同的(有争议的)问题...... (6认同)
  • @sleske 这不仅仅是为了最小化它们的范围:在许多情况下,这是创建变量 `const` 的唯一合理方法(例如,当值取决于在新变量初始化之前执行了语句时)。如果您的所有变量都在块的开头声明,您就没有机会跟踪应该是常量的意外更改。 (2认同)