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

为什么旧的C语言规范要求预先声明函数局部变量?,c,compiler-construction,language-design,C,Compiler Construction,Language Design,在C编程语言中,在计算任何非声明性/赋值表达式之前,我使用的所有语言修订都是强制的预先变量声明。C++似乎已经放弃了所有版本的需求。我也承认更现代的C语言版本也放弃了这一要求,但我还没有使用这些标准中的任何一个 我的问题是:有什么历史原因阻止C语言自由地按需而不是预先声明? 显然,从工程学的角度来看,我想到了很多原因,但对我来说,没有一个是特别合理的 防止发生不明显的编译器行为错误(例如无限的解析循环、用于计算的大量内存膨胀,或者宏的一些奇怪的情况) 防止不需要的编译器输出。这可能是从使调试过程

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

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

显然,从工程学的角度来看,我想到了很多原因,但对我来说,没有一个是特别合理的

  • 防止发生不明显的编译器行为错误(例如无限的解析循环、用于计算的大量内存膨胀,或者宏的一些奇怪的情况)
  • 防止不需要的编译器输出。这可能是从使调试过程混乱的符号输出和调试工具开发的方便性,到意外的堆栈存储顺序
  • 可读性。我发现这也很难接受,因为C语言虽然是为可读性而设计的,但与当时的其他语言相比,几乎在其他任何地方都没有采用这种结构。(除非您将原型设计视为一种类似的强制措施,但如果我记得原型是在'89规范中添加的。)
  • 实现的复杂性和实际原因。这是我最倾向于相信的。作为工程师,我们必须做出某些考虑,以便在分配的时间范围内装运可行的产品。虽然我承认计算机科学和软件工程的专业领域都发生了巨大的变化,但商业仍然是商业。最后,我肯定Bell想要一个可以在Unix编程环境中使用的成品来展示他们所取得的成就

  • 有没有人有任何好的来源支持上述任何一项?我完全错过了什么吗?我们可以从黎明到黄昏进行推测,但我正在寻找好的硬参考。

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

    void foo()
    {
        int i = 5;
        printf("%i\n", i);
    }
    
    当gcc将此函数转换为汇编代码时,对foo()的调用将归结为一系列指令,包括为函数作用域设置stackframe。此stackframe包含函数范围中定义的变量的空间,为了匹配更高级语言C中的相同范围,需要在块的开头定义这些变量

    最后,它是关于实现的简单性和效率,因为在一个块的开始,它同时声明了一组变量,使编译器能够将它们批量推送到堆栈上,这也是一个性能考虑因素


    当然,这个答案非常简单,目的只是简单介绍一下为什么会这样做。有关更多信息,您可能应该阅读早期C89标准的一些草案。

    一个简短的答案实际上回答不多:C语言最初继承了它的前身:B语言的声明顺序限制。不幸的是,我不知道为什么用B语言这么做

    还请注意,在新生的C语言(如中所述)中,使用非常量表达式初始化变量(甚至是局部变量)是非法的

    int a 5;
    int b a; /* ERROR in nascent versions of C */
    
    (旁注:在CRM初始化中,语法不包括
    =
    字符)。在一般情况下,这实际上否定了代码内变量声明的主要好处:能够将有意义的运行时值指定为初始值设定项。即使在更现代的C89/90中,这种限制仍然正式应用于聚合初始值设定项(尽管大多数编译器忽略了它)

    只有在C99中,才有可能对所有类型的本地初始化使用运行时值。这最终解锁了代码内变量声明的全部功能,因此C99是引入它们的人,这是完全合乎逻辑的。

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

    void foo()
    {
        int i = 5;
        printf("%i\n", i);
    }
    
    function语句只是一个复合语句,它的开头可能有声明

    函数语句:{声明listopt语句列表}

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

    声明列表:声明列表opt

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

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

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

    1975年手册第11.1节明确规定:

    C不是块