为什么旧的C语言规范需要预先声明函数局部变量?

时间:2013-01-14 18:42:58

标签: c compiler-construction language-design

在C编程语言中,我在使用任何非声明性/赋值表达式之前对所有语言修订都强制执行前面的变量声明。 C ++似乎已经放弃了所有版本的这一要求。我也认识到更多现代版本的C也放弃了这个要求,但我还没有使用任何这些标准。

我的问题是:有什么历史原因阻止C语言按需而不是预先声明?

显然,从工程的角度来看,有很多原因可以解释,但对我来说,似乎没有任何理由可以合理。

  1. 防止发生模糊的编译器行为错误(例如无限的解析循环,用于评估的大量内存膨胀,或者使用宏的一些奇怪的角落情况。)
  2. 防止不需要的编译器输出。这可能是从调试过程混乱的符号输出和调试工具的易开发到意外的堆栈存储命令。
  3. 可读性。我发现这很难被吞下去,看作是C,虽然与其他语言相比设计的可读性,但几乎没有在其他任何地方强制执行这种类型的结构。 (除非您将原型设计视为类似的执行,但如果我记得在'89规范中添加了原型。)
  4. 实施复杂性和实际原因。这是我最倾向于相信的。作为工程师,我们必须做出一些考虑,以便在分配的时间范围内发运可行的产品。虽然我认为计算机科学和软件工程的专业领域都发生了巨大变化,但业务仍然是业务。在一天结束时,我确信贝尔想要一个可以在Unix编程环境中使用的成品来展示他们已经取得的成就。
  5. 有没有人有任何好的消息来支持上述任何一项?我完全错过了什么吗?我们可以从黎明到黄昏进行推测,但我正在寻找好的参考资料。

3 个答案:

答案 0 :(得分:18)

从Dennis Ritchie的主页看早期(第6版Unix,1975)C manual,在该版本中,函数 - 局部变量只能在函数的开头声明

  

function-statement只是一个复合语句,可能在开头有声明。

     
    

function-statement :{ declaration-list opt statement-list }

  

声明列表未定义(省略),但可以很容易地假设有语法:

  

声明列表声明 声明列表 opt

不允许其他复合语句包含变量(或实际上任何)声明。

这显然简化了实施;在早期的编译器源代码c02.c中,函数头函数blkhed()只需要对auto变量声明所使用的堆栈空间求和,同时记录它们的堆栈偏移量,并将代码发送到将堆栈指针缓冲适当的量。在函数退出(通过return或从结束处掉下来),实现只需要恢复保存的堆栈指针。

事实上,K& R认为必须声明“变量声明(包括初始化)可能跟随引入任何复合语句的左括号,而不仅仅是开始函数的那个”是暗示在那一点上它是一个相对较新的特征。它还表明组合的声明初始化语法也是最近的一个特性,实际上在1975年手动声明器中也没有初始化器。

第11.1节中的1975年手册明确指出:

  

C不是块结构语言;这可能被认为是一种缺陷。

块语句和初始化声明(K& R)解决了这个缺陷,混合声明和代码(C99)是逻辑延续。

答案 1 :(得分:10)

在C89中,变量定义必须位于块的开头。 (参见块标准定义的C标准)据我所知,这简化了汇编程序中处理变量的方式。例如,让我们看一个简单的函数:

void foo()
{
    int i = 5;
    printf("%i\n", i);
}

当gcc将此函数转换为汇编代码时,对foo()的调用将归结为一堆指令,包括为函数范围设置堆栈帧。该堆栈帧包含在函数范围内定义的变量的空间,并且为了匹配更高级语言C中的相同范围,它们需要在块的开头定义。

最后,它是关于易于实现,还是效率,因为一次声明一堆变量,它在块的开头,使编译器能够在堆栈上批量推送它们,并且约为89,这也是一个性能考虑因素。

当然,这个答案非常简单,只是为了简要说明为什么这样做已经完成了。有关更多信息,您应该阅读早期C89标准的一些草稿。

答案 2 :(得分:9)

一个简短的答案并没有真正回答:C语言最初从其前身:B语言继承了这个声明顺序限制。为什么用B语言这样做我很遗憾,不知道。

另请注意,在新生的C(在"C Reference Manual"中描述)中,使用非常量表达式初始化变量(甚至是本地变量)是非法的。

int a 5;
int b a; /* ERROR in nascent versions of C */

(旁注:在CRM初始化语法中不包含=字符)。在一般情况下,这有效地否定了代码内变量声明的主要好处:能够将有意义的运行时值指定为初始化器。即使在更现代的C89 / 90中,这种限制仍然正式应用于聚合初始化器(尽管大多数编译器都忽略了它)

int a = 5, b = a;
struct { int x, y; } s = { a, b }; /* ERRROR even in C89/90 */ 

只有在C99中才能使用运行时值进行各种本地初始化。这最终释放了代码内变量声明的全部功能,因此C99引入它们是完全合乎逻辑的。