基于这种有趣的差异,C声明实际上是如何解析的?

How are C declarations actually parsed, based on this interesting discrepancy?

提问人:Aaron Linnell 提问时间:6/19/2023 最后编辑:LundinAaron Linnell 更新时间:6/19/2023 访问量:94

问:

将声明和赋值组合在一起是很常见的:

int beans = a * 2;

或者分开,像这样

int beans;
beans = a * 2;

我目前的理解是,豆子可以被赋值,因为它是一个左值;它具有可以写入的存储。不能赋值 rvalue,因为它只是一个带有值的表达式,没有存储。因此,允许这样做:a * 2

int beans;
(beans) = a * 2;

事实上,任何左值赋值的左操作数都应该有效。现在,这似乎表明这是一个表达式,也是一个左值。但是,这是不允许的:int beans;

(int beans) = a * 2;

就 C 解析器的设计方式而言,这表明声明不仅仅是一个带有左值的表达式。这是怎么回事?

c 解析 初始化 语言-律师 赋值-运算符

评论

1赞 user17732522 6/19/2023
in 根本不是赋值。声明和表达式语句只是语言中两个完全独立的语法规则。实际上没有什么可以比较你试图的方式。=int beans = a * 2;
1赞 Aaron Linnell 6/19/2023
你是说 = 是声明的可选元素,而不是在语法上与赋值相同,因为它自己的语句?
2赞 user17732522 6/19/2023
是的,只是碰巧在两种不同的语法生产中用作标记,一次在声明的初始化声明器中,一次在赋值表达式中用作赋值运算符=
0赞 Erdal Küçük 6/19/2023
这就是所谓的初始化:en.cppreference.com/w/c/language/initialization vs. en.cppreference.com/w/c/language/declarations
3赞 Tom Karzes 6/19/2023
@AaronLinnell 声明中的 optional 引入了初始值设定项。虽然与作业有相似之处,但也有许多不同之处。除此之外,您可以初始化数组,但不能分配给它们。试试这个: .你不能在作业中做这样的事情。=int a[3] = {1, 2, 3};

答:

6赞 Some programmer dude 6/19/2023 #1

声明

beans = a * 2;

包含许多表达式。

主要表达式是赋值本身:.这反过来又包含两个子表达式:和 .乘法表达式本身具有子表达式:和 。beans = a * 2beansa * 2a2

所有表达式都可以用括号括起来,这意味着整个语句可能如下所示:

(beans) = ((a) * (2));

这里所有的子表达式都用括号括起来。

现在我们来谈谈定义:

int beans;

这不是一个表达方式。它不包含任何子表达式。它不能作为一个整体括起来。

另一方面,初始化的定义:

int beans = a * 2;

do 包含一个带有子表达式的表达式。那是在 .所以我们可以把它写成:=

int beans = ((a) * (2));

但同样,变量声明部分不是表达式,不能用括号括起来。


另请注意,在定义中不是赋值。这是初始化。这两个不同的术语表明存在语义差异。=


正如乔纳森·莱夫勒(Jonathan Leffler)的评论中提到的。声明的声明部分可以用括号括起来。

对于一个简单的声明,例如

int beans;

使用它真的没有意义。但是对于指向函数的指针或指向数组的指针之类的东西,它有很大的不同。

例:

int *foo(int x);

它声明了一个函数,该函数接受一个参数并返回指向 的指针。与以下内容进行比较:intint

int (*foo)(int x);

它声明了一个变量,该变量是指向函数的指针。该函数接受一个参数,并返回一个值。intint

评论

4赞 Jonathan Leffler 6/19/2023
请注意,您可以在“声明符”中包含括号:或 — 甚至(您可以在标量的初始值设定项周围包含大括号)。该标准的相关部分是§6.7.6声明者int (beans);int (beans) = 3;int ((((beans)))) = { 3 };