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

huangapple go评论61阅读模式
英文:

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

问题

很常见看到声明和赋值结合在一起,像这样:

int beans = a * 2;

或者分开写,像这样:

int beans;
beans = a * 2;

我目前的理解是,beans可以被赋值,因为它是一个左值(lvalue),它有可以被写入的存储空间。而像 a * 2 这样的右值(rvalue)不能被赋值,因为它只是一个具有值的表达式,没有存储空间。因此,这是允许的:

int beans;
(beans) = a * 2;

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

(int beans) = a * 2;

从C解析器的设计角度来看,这表明声明不仅仅是一个具有左值的表达式。这里发生了什么?

英文:

It's fairly common to see a declaration and assignment combined like this:

int beans = a * 2;

or separate, like this

int beans;
beans = a * 2;

My current understanding is that beans can be assigned to because it is an lvalue; it has storage that can be written to. An rvalue, like a * 2 cannot be assigned to since it's just an expression with a value, no storage. For this reason, this is allowed:

int beans;
(beans) = a * 2;

And in fact, any left operand to the assignment which is an lvalue should work. Now, this seems to suggest that int beans; is an expression which is also an lvalue. However, this is not allowed:

(int beans) = a * 2;

In terms of how the C parser is designed, this suggests that declarations are something greater than just an expression with an lvalue. What is going on here?

答案1

得分: 6

以下是翻译好的部分:

这个语句

beans = a * 2;

包含许多表达式。

主要的表达式是赋值本身:beans = a * 2。它又包含了两个子表达式:beansa * 2。乘法表达式本身又有子表达式:a2

所有表达式都可以用括号括起来,这意味着整个语句可以改写成这样:

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

这里所有的子表达式都被括起来了。

现在我们来看看这个定义:

int beans;

那不是一个表达式。它不包含任何子表达式。不能把它整体括起来。

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

int beans = a * 2;

包含一个表达式,带有子表达式。而且这在 =右边。所以我们可以写成:

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

但是再次强调,变量声明部分不是一个表达式,不能被括起来。


另请注意,定义中的 = 不是 赋值。它是初始化。这两个不同的术语表示了语义上的区别。


正如Jonathan Leffler在评论中提到的,声明的声明符部分可以被括起来。

对于像

int beans;

这样的简单声明来说,使用它确实没有太多意义。但是对于指向函数或数组的指针之类的事情来说,这会有很大的区别。

示例:

int *foo(int x);

这声明了一个接受一个 int 参数并返回一个指向 int 的指针的函数。与下面的比较一下:

int (*foo)(int x);

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

英文:

The statement

beans = a * 2;

contains many expressions.

The main expression is the assignment itself: beans = a * 2. That in turn contains two sub-expressions: beans and a * 2. The multiplication expression have sub-expressions itself: a and 2.

All expressions can be parenthesized, which means the whole statement could look like this instead:

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

Here all the sub-expressions are parenthesized.

Now we come to the definition:

int beans;

That is not an expression. It doesn't contain any sub-expressions. It can not be parenthesized as a whole.

The definition with initialization, on the other hand:

int beans = a * 2;

do contain an expression, with sub-expressions. And that is on the right side of the =. So we can write it as:

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

But again, the variable declaration part is not an expression, and can't be parenthesized.


Also please note that = in a definition is not assignment. It's initialization. There's a semantic difference indicated by the two different terms.


As mentioned in a comment by Jonathan Leffler. the declarator part of a declaration can be parenthesized.

For a simple declaration like

int beans;

it really makes no sense to use it. But for things like pointers to functions or pointers to arrays it makes a large difference.

Example:

int *foo(int x);

That declares a function that takes one int arguments and returns a pointer to int. Compare to the following:

int (*foo)(int x);

That declares a variable, which is a pointer to a function. The function takes one int argument, and returns an int value.

huangapple
  • 本文由 发表于 2023年6月19日 00:55:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76501647.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定