在以下情况下多重分配如何工作?

How the multiple assignment worked in the below case?

我刚刚写了一个示例代码来试用一下。令人惊讶的是,我没有遇到任何编译失败。根据 C,我们应该在初始化或使用之后进行声明。请解释一下。

#include <stdio.h>

int main(void) {

    int a = a = 1; //Why it compiles??
    printf("%d",a);
    return 0;
}

以上代码编译成功并输出 1。请解释并提供允许此操作的标准输入。

定义

int a = a = 1;

等于

int a = (a = 1);

并且大致等同于

int a;
a = (a = 1);

当你在初始化中使用a时,它已经被定义,存在并且可以被赋值。更重要的是,由于它已被定义,因此可以用作其自身初始化的来源。

a = 1 这样的每个赋值表达式都有 - 除了将值 1 赋值给 a 的 "side effect" - 一个结果值,即 [= 的值12=]赋值后(cf,例如cppreference/assignment):

Assignment also returns the same value as what was stored in lhs (so that expressions such as a = b = c are possible).

因此,如果您写 int a; printf("%d",(a=1)),输出将是 1

如果您知道 int a; a = a = 1 中的链式赋值,那么这等同于 int a; a = (a=1),并且 - 因为 (a=1) 的结果是 1,结果a = (a=1) 也是 1

C 标准没有定义这种情况下的行为,不是因为关于未排序的效果或显式语句的规则,而是因为它未能解决这种情况。

C 2011(非官方草案 N1570)第 6.7 条第 1 段向我们展示了声明的整体语法。在这个语法中,int a = a = 1; 是一个 声明 其中:

  • int 是一个 声明说明符 ,它仅由 类型说明符 int 组成。
  • a = a = 1 是一个 init-declarator,其中 a 是一个 declaratora = 1 是一个 初始化器 声明符 仅由 标识符 a.
  • 组成

6.7.6 3 定义一个 full declarator 是一个不属于另一个 declarator 的声明符,它说 full declarator 的结尾是一个序列点。然而,这些对于我们的分析来说并不是必需的。

6.7.9 8 说“初始化程序指定存储在对象中的初始值。”

6.7.9 11 说“标量的初始值设定项应为单个表达式,可选地括在大括号中。对象的初始值为表达式的初始值(转换后);应用与简单赋值相同的类型约束和转换,将标量的类型作为其声明类型的非限定版本。”

因此,一方面,值为 1 的初始化程序指定存储在 a 中的初始值。另一方面,表达式 a = 1 具有在 a 中存储 1 的副作用。我在 C 标准中没有看到任何说明哪个先出现的内容。有关表达式内排序的规则仅适用于初始化程序的评估;他们没有告诉我们将“初始值”赋予 a 的顺序以及分配给它的副作用。

可以合理推断,无论a赋初值1还是赋值1,最终都是1,所以定义了行为。然而,该标准以未定义的方式以无序的方式两次修改对象的值而闻名,即使写入的值是相同的。该规则的显式声明在 6.5 2 中,它适用于表达式,因此不适用于我们在初始化和表达式之间存在冲突的这种情况。但是,我们可以将标准的精神解释为:

  • 为了提供一个实现机会来做任何需要做的事情来存储(或修改)对象中的新值,必须定义存储相对于其他存储(或修改)的顺序。
  • 该标准未能为初始化和赋值副作用定义顺序,因此无法为实现提供所需的约束。

因此,标准未能以保证实现将产生定义行为的方式指定行为。

此外,我们可以考虑int a = 2 + (a = 1)。在这种情况下,初始化程序的值为 3,但副作用将 1 分配给 a。对于这个声明,标准并没有说明哪个值占优势(除了有人可能从字面上解释“初始值”,因此暗示必须先分配 3,因此副作用必须稍后)。