【问题标题】:How the multiple assignment worked in the below case?在以下情况下,多重分配是如何工作的?
【发布时间】:2018-09-29 12:29:05
【问题描述】:

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

#include <stdio.h>

int main(void) {

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

以上代码编译成功并输出 1。请解释并提供任何允许这样做的标准输入。

【问题讨论】:

  • 使用 symply int a = 1;
  • 您使用哪个编译器?更重要的是:您传递给编译器的标志是什么?
  • 编译器可能只是忽略了其中的一部分。
  • 您有什么理由认为应该得到编译错误?这是有效的 C(但也是有效的废话)。您可能想接受这两个概念并不排斥。

标签: c initialization variable-assignment


【解决方案1】:

a = 1 这样的每个赋值表达式 - 除了将值 1 赋值给 a 的“副作用”之外 - 一个结果值,即赋值后 a 的值(参见,例如, cppreference/assignment):

Assignment 还返回与存储在 lhs 中的值相同的值(所以 a = b = c 这样的表达式是可能的)。

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

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

【讨论】:

  • 而且由于a = 1 是第一个a 的左操作数a 的副作用,因此初始化会调用未定义的行为。
  • int a = a = 1; 中只有一个赋值运算符,第一个 = 符号是初始化语法的一部分。和int a; a = a = 1;不一样
  • @M.M 一项任务,但两项 assignment-expressions.
  • @Lundin 在技术上是三个,因为 a1 都匹配语法类别 assignment-expression(因为 primary-expression 是它的子集)。但这与问题没有太大关系
【解决方案2】:

定义

int a = a = 1;

等于

int a = (a = 1);

并且也大致相当于

int a;
a = (a = 1);

当你在初始化中使用a时,它已经被定义了,它存在并且可以被赋值。更重要的是,既然它被定义了,那么它就可以用作它自己的初始化源。

【讨论】:

  • int a = a = 1;int a; a = (a = 1); 之间没有等价关系。后者调用undefined behavior。前者恰好没有,因为end of a full initialization expression处有一个序列点,这里Tsnippet的输出也表明后者是UB。
  • @PascalCuoq 这就是为什么我说“大致等效”。是的,它是 UB,因为分配的副作用(a1 的实际设置)没有排序,有两个 a = 1 分配可以按任何顺序发生。结果仍然是 a 被赋值为 1
  • 未定义的行为没有结果。
  • @Lundin 如果你拒绝将附件 C 视为非规范,我已经知道你会如何看待这个论点,但是 Clang 和 GCC,质量编译器都会警告 x = (x = 1);,忽略关于int x = x = 1;。可以争辩说该标准是模棱两可的,但我们中似乎有几个人将其解释为定义了int x = x = 1;
  • @Lundin 使用int a = a = 1;(这不是挑剔的表达式)你有int a,其中a 是声明符。然后你有 a = 1 这是初始化程序。
【解决方案3】:

C 标准没有定义这种情况下的行为,不是因为关于无序列效果或显式声明的规则,而是因为它没有解决这种情况。

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

  • int 是一个 declaration-specifiers,仅由 type-specifier int 组成。
  • a = a = 1 是一个init-declarator,其中a 是一个declaratora = 1 是一个初始化器声明符仅由标识符a组成。

6.7.6 3 将 full declarator 定义为不属于另一个 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,因此副作用必须在后面)。

【讨论】:

    猜你喜欢
    • 2013-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-13
    • 2020-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多