在以下情况下多重分配如何工作?
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
是一个 declarator 和 a = 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,因此副作用必须稍后)。
我刚刚写了一个示例代码来试用一下。令人惊讶的是,我没有遇到任何编译失败。根据 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
是一个 declarator 和a = 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,因此副作用必须稍后)。