为什么声明不是 C 中的语句?

Why a declaration is not a statement in C?

下面的例子是非法的C程序,很容易混淆,表明声明不是C语言中的语句。

int main() {
  if (1) int x;
}

我读过C的规范(N2176),我知道C语言在语法规范中区分声明和语句。我告诉我教编译器的老师,他似乎不相信,除非我把规范给他看,否则我无法说服他。

所以,我也很迷茫。为什么C要这样设计?为什么声明不是 C 中的语句?如何让别人相信这种设计的原因?

这是因为声明不会指示编译器任何事情,它纯粹是为编译器提供信息;至少按照标准。如果编译器看到一个声明,他们可能会做一些事情,标准也不禁止它,但如果他们只看到一个声明并且它声明的任何内容从未在任何语句中使用,它不要求他们做任何事情。

考虑这段代码:

int main ( ) 
{
    int x;
    printf("Hello World!\n");
    return 0;
}

你认为 int x; 会做什么?您声明 xint 类型,但您永远不会在其余代码的任何地方使用 x 。编译器甚至不必为它在堆栈上保留任何内存。可能是这样,但不是必须这样做。

该标准允许编译器创建与您编写的代码完全相同的代码:

int main ( ) 
{
    printf("Hello World!\n");
    return 0;
}

如果让编译器知道变量的类型,那么编译器无需做任何事情。这个变量根本不必存在于任何地方,除非它曾经被语句使用过。

C 不是一种解释型语言,每段代码都指示解释器直接做某事。 C 是一种编译语言,这意味着您告诉编译器为您生成 CPU 代码,以执行您在预定义语言中描述的操作。所以你写的代码和编译器生成的CPU代码之间没有一对一的关系。

你可以写

int x = a / 8;

但编译器生成的 CPU 代码可能等同于

int x = a >> 3;

因为这是完全一样的事情,如果移位比除法快(你可以打赌它是),编译器不必仅仅因为你告诉它这样做就生成除法。你告诉编译器的是“我希望 x 是 a 的八分之一”,编译器会说“好吧,我会生成实现这一目标的代码”,但编译器如何实现它取决于编译器。

因此编译器只需要将语句翻译成CPU代码。实际上,只有那些有效的语句才可能需要进行昂贵的分析,因此将所有语句转换为代码并不违反标准,即使是那些什么都不做的语句。声明本身从来没有效果,它只是让编译器知道变量或函数的类型,这在以后的语句中可能会变得很重要,但前提是 variable/function 被实际使用过。

如果有效的,您希望这个程序做什么? :


#include <stdio.h>

int main (int argc, char **argv)
{
if (argc > 1) int x=42;

printf("%d\n", x);
return 0;
}

因为没有明显的语法或技术语义原因声明不能出现在语句可能出现的地方,这似乎主要是由于历史和缺乏实用性。

考虑语法

语句在函数定义规则中进入C语法,其中出现复合语句复合语句允许语句。然后检查 statement 的替换会发现 statement 可能出现但 declaration 可能不会出现的地方:

  • labeled-statement 中,标签后跟 :
  • 选择语句 中,在 ifswitch) 之后或 else 之后.
  • 迭代语句 中,在 whilefor) 之后或 do 之后。

以下不是对语法的正式分析,但它出现了语句可能出现但声明可能不是很有限的地方:结束标签的:之后,关键字之后(elsedo) 或紧跟在关键字 (if, switch, [=16] 后的 ( 结束 ) =],或 for)。在我看来,这些似乎是语法中的明确点,在 复合语句 中的 ; 之后,应该很容易区分声明和语句。 .

因此,我认为在语法上没有理由不允许声明出现在语句可能出现的任何地方(或者,等效地,将声明定义为一种语句)。

考虑语义

现在考虑在当前可能出现语句但可能不会出现声明的地方允许声明的语义效果。

labeled-statement 的情况下,我们希望有 <i>label</i>:<i>声明</i>,我们可以使用<i>标签</i>:; <i>声明</i>,我们在:之后插入了一个空语句。结果是一个定义的代码序列,其语义效果等同于我们希望通过允许在标签后立即声明来获得的效果。

在其他情况下,我们希望有<i>声明</i>,我们可以使用{ <i>声明</i>}。同样,结果是一个定义的代码序列,其语义效果等同于我们希望通过允许裸声明获得的效果。这种影响很小;声明中的任何表达式(在数组声明符或初始化器中)都将被计算,但声明的任何内容都会立即超出范围。请注意,即使作用域没有被结束 } 结束,它也会因为 C 标准将这些地方中的每一个都定义为块这一事实而结束。 (C 2018 6.8.4 3 说 selection-statement 的子语句是块,6.8.5 5 说 iteration-statement[= 的循环体86=] 是一个块。)

尽管如此,这表明在声明可能出现的任何地方都没有技术语义障碍。

结论

由于 C 的语法和语义显然不排除允许声明是一种语句类型,因此我们留下了历史和效用的原因。在 Kernighan 和 Ritchie 的 The C Programming Language 第一版中描述的 C 中,声明的位置是有限的。在函数内部,它们只能出现在复合语句的开头。声明不能跟在声明之后。正如我们从现代 C 中看到的那样,这种限制没有语法或语义上的原因;我们可以允许在复合语句中的任何地方声明。所以这似乎很简单,大约在 1978 年,语言方面的工作并没有取得那么大的进展。

同样,目前关于 C 的工作似乎还没有达到允许声明出现在语句可能出现的任何地方的程度,就好像它是一种语句,即使可能没有技术障碍。但是,在这种情况下,放松规则的动机较小。在上述情况中,唯一有用的是允许在标记语句中进行声明。而且,由于通过插入 null 语句可以轻松实现其预期效果,因此可能没有足够的动力来更改编译器和倡导 C 委员会的更改。