C89,变量声明与代码混合

6

我非常好奇,为什么当您尝试混合变量声明和代码时,例如以下内容,C89编译器会拒绝编译:

rutski@imac:~$ cat test.c
#include <stdio.h>

int
main(void)
{
    printf("Hello World!\n");
    int x = 7;
    printf("%d!\n", x);
    return 0;
}
rutski@imac:~$ gcc -std=c89 -pedantic test.c
test.c: In function ‘main’:
test.c:7: warning: ISO C90 forbids mixed declarations and code
rutski@imac:~$ 

是的,您可以通过避免使用-pedantic来避免这种情况。但是,这样做会使您的代码不再符合标准。正如任何能够回答此帖子的人可能已经知道的那样,这不仅仅是一个理论上的问题。像Microsoft的C编译器这样的平台在任何情况下都会迅速实施标准。
考虑到C语言的古老性质,我想这个特性可能是由于70年代非常有限的硬件条件所导致的一些历史问题,但我不知道具体细节。或者我完全错了吗?

嘿。已经有一段时间了,但我记得它与在特定平台上无法混合声明/堆栈分配和代码有关。后来,嵌套块被添加以允许在这些平台上进行有限形式的混合(将它们视为内部函数,或多或少)。我找不到参考资料,而且这是很久以前的事情,所以我不太相信我的记忆,这就是为什么这个问题没有被列入答案的原因。 - geekosaur
3
为什么不问DMR呢?我已经给他发了一封电子邮件(他的地址可以在他的网站上公开获得)。如果我收到任何回复,我会在这里发布......尽管我更希望他创建一个账户并亲自回答 :-) - Duncan Bayne
4个回答

7
C标准中说“不可以”,因为早期的C编译器不允许这种用法,而C89标准化了这种用法。创建一种用于编写操作系统及其实用程序的语言是一个足够激进的步骤。这个概念可能根本没有被考虑过 - 当时没有其他语言允许这样做(Pascal、Algol、PL/1、Fortran、COBOL),所以C也不需要。同时,这可能会使编译器稍微难以处理(更大),而最初的编译器受到PDP 11系列机器64 KiB代码和64 KiB数据空间的限制(大型机器;较小的机器只允许64 KiB的代码和数据,据我所知)。因此,增加复杂性并不是一个好主意。
C++允许声明和变量交错,但C++不是C,也从未是C。然而,C99终于赶上了C++(这是一个有用的特性)。然而,遗憾的是,微软从未实现C99。

1
我对K&R C很好奇。在K&R C中,声明是否必须从每个函数块的开头开始,或者允许在任何块的开头进行声明?我的意思是foo() { int i; for(i=0; i<n; i++) { int j; for(j=0; j<n; j++) {}}}在K&R C中是否合法? - Z boson
@Zboson:预标准C允许在大括号{...}内的任何复合语句开头定义变量。虽然存在初始化限制,但本质上并没有对定义进行限制。标准在很大程度上遵循了现有的实践。 - Jonathan Leffler

4

可能从来没有以这种方式实现,因为从来没有需要。

假设您想在普通的C语言中编写类似于以下内容的东西:

int myfunction(int value)
   {
   if (value==0)
      return 0;
   int result = value * 2;
   return result;
   }

那么您可以轻松地将其重写为有效的C语言,如下所示:
int myfunction(int value)
   {
   int result;
   if (value==0)
      return 0;
   result = value * 2;
   return result;
   }

首先声明变量,然后设置其值不会对性能造成任何影响。

然而,在C++中就不是这样了。在下面的例子中,function2将比function1慢:

double function1(const Factory &factory)
   {
   if (!factory.isWorking())
      return 0;
   Product product(factory.makeProduct());
   return product.getQuantity();
   }

double function2(const Factory &factory)
   {
   Product product;
   if (!factory.isWorking())
      return 0;
   product = factory.makeProduct();
   return product.getQuantity();
   }

在function2函数中,即使工厂不工作,也需要构造产品变量。随后,工厂制造产品,然后赋值运算符需要复制产品(从makeProduct的返回值到产品变量)。在function1函数中,只有在工厂工作时才会构造产品,即使是这样,也调用复制构造函数而非普通构造函数和赋值运算符。
然而,我期望现代的好的C++编译器会优化这段代码,但在最初的C++编译器中可能不是这种情况。
第二个例子如下:
double function1(const Factory &factory)
   {
   if (!factory.isWorking())
      return 0;
   Product &product = factory.getProduct();
   return product.getQuantity();
   }

double function2(const Factory &factory)
   {
   Product &product;
   if (!factory.isWorking())
      return 0;
   product = factory.getProduct();    // Invalid.  You can't assign to a reference.
   return product.getQuantity();
   }

在这个例子中,function2 是无效的。引用只能在声明时分配值,而不能在以后。 这意味着在这个例子中,编写有效代码的唯一方法是在变量真正初始化的时候写出声明。不要更早地写。 这两个例子都说明了为什么在 C++ 中允许在其他可执行语句之后声明变量,而不像在 C 中一样在块的开头声明。 这解释了为什么这被添加到 C++ 而不是 C(和其他语言)中,因为它并不是真正需要的。

类似地,大多数 C89 编译器也会优化代码,并且只有在函数中实际使用变量时才在栈上分配它们。 - Lundin

2

这类似于要求在使用函数之前声明它们 - 它允许一个简单的编译器在顺序处理源码的同时,逐步生成目标代码。

在这种情况下,编译器可以浏览声明,并累加所需的堆栈空间。当它到达第一条语句时,它可以输出调整堆栈的代码,为局部变量分配空间,并立即位于函数代码开始之前。


0

对于需要在函数开始时声明所有变量的语言来说,编写编译器要容易得多。有些语言甚至要求在函数代码之外的特定子句中声明变量(比如 Pascal 和 Smalltalk)。

原因是如果已知变量并且不会改变,将这些变量映射到堆栈(或者如果你的编译器够聪明则映射到寄存器)会更容易。

任何其他语句(尤其是函数调用)都可能修改堆栈/寄存器,从而使变量映射更加复杂。


Pascal 允许在块的开始处定义变量 - 以及在任何块外部定义全局变量。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接