【问题标题】:C: transitive (double) assignmentsC:传递(双重)分配
【发布时间】:2016-12-12 19:19:27
【问题描述】:

我在 C 中使用过这样的结构:

list->head = list->tail = NULL;

现在我考虑这是否真的意味着我想的那样。

这是什么意思?

  1. list->head = NULL; list->tail = NULL;

  1. list->head = list->tail; list->tail = NULL;

谢谢澄清

【问题讨论】:

  • 从右到左阅读。
  • 正式没有!
  • 真正的答案就是不要那样做。如果您需要将两个指针设置为空,请在单独的行中进行。然后,任何编程背景的人都可以立即看到其意图和代码的作用。

标签: c variable-assignment assignment-operator


【解决方案1】:

这些都不对。

由于简单赋值 = 运算符是从右到左关联的,因此您的表达式等同于:

list->head = (list->tail = NULL);

NULL 被分配给tail,然后具有空指针值的tail 被分配给head。

【讨论】:

  • 是的,我的意思是,但这也是选项,而不是分配顺序的结果。所以 1) 选项将是正确的,因为两者都有 NULL 指针。谢谢进一步澄清
  • @MichałZiobro 选项 1 具有示例中不存在的序列点。
  • 我真的很想阅读建设性的批评以及反对意见。
  • 抱歉,必须进行编辑才能删除 DV。我认为volatile 有问题。检查了标准。虽然确实存在问题,但标准并未定义如何解决它,让您的答案大部分正确。但因此,这不应该与 volatile 合格的对象一起使用!
  • @Olaf 发布一个自我回答的帖子,而不是在不相关的问题上将此信息隐藏在 cmets 中。其他人希望从您对 volatile 的了解中受益。
【解决方案2】:

赋值运算符是从右到左结合的。

因此这个表达式语句

list->head = list->tail = NULL;

等价于

list->tail = NULL;
list->head = list->tail;

【讨论】:

    【解决方案3】:

    他们都不是。意思是

    list->tail = NULL;
    list->head = list->tail;
    

    1 不正确,因为如果list->tail 的类型不是指针,则可能会产生不同的结果,因为该值将转换为赋值目标的类型。

    2中两个语句的顺序不正确。

    【讨论】:

    • 抱歉,必须进行编辑才能删除 DV。我认为volatile 有问题。检查了标准。虽然确实存在问题,但标准并没有定义如何解决它,让您的答案大部分正确。但因此,这不应该与 volatile 合格的对象一起使用!
    【解决方案4】:

    这是多重分配,因为您的第一个选项是正确的

    list->head = list->tail = NULL;
    

    这是一种流动的魔法。

    最初,tail 设置为NULL,从右到左开始

    list->tail = NULL;
    

    那么 list->head = list->tail ;

    现在tailNULL,所以head 也将分配一个NULL

    【讨论】:

      【解决方案5】:

      赋值运算符= 的结合性是从右到左的。这意味着,如果一个语句中有多个 = 运算符,则首先计算最右边的运算符,然后计算它左边的运算符,并按此顺序,直到最后计算最左边的 = 运算符。

      这意味着当你这样做时

      list->head = list->tail = NULL;
      

      最右边的赋值,即list->tail = NULL首先被评估。所以list->tail 将是NULL

      之后将评估list->head = list->tail。由于list->tail 现在是NULL(因为之前的评估——即list->tail = NULL),现在list->head 也是NULL

      根据您在问题中的表达方式,它就像

      list->tail = NULL; list->head = list->tail;
      

      【讨论】:

      • NULL 是一个宏,而不是一个值。
      • 抱歉,必须进行编辑才能删除 DV。我认为volatile有问题。检查了标准。虽然确实存在问题,但标准并未定义如何解决它,让您的答案大部分正确。但因此,这不应该与 volatile 限定对象一起使用!
      • @Olaf 同意,我应该小心那里的话。无论如何,只是想知道,我们可以使用 NULL 作为标识符吗?喜欢struct node *NULL = &node; 吗?我的意思是可以使用不是宏的NULL 吗?
      • 宏只是预处理器的标识符。对于普通的编译器,它只是不存在。 (而且你也不应该将它用作一个)。 AFAIK 标准不允许应用程序重新定义这些宏。正如@JohnBode 所写:这是一个严格的不行(如果我看到同事的这种黑客行为,我会认真地踢但是)!是的,它是一个宏。随意在标准中查找自己。阅读/理解并不难。
      • @sps:这是标准的。请参阅 C 2011 标准的 online draft 的 7.19。
      【解决方案6】:

      对于所有不完整的答案,我必须澄清一下。

      首先,表达式从右到左求值:

      list->head = (list->tail = NULL);
      

      标准将behaviour of the assignment operator定义为:

      赋值运算符将值存储在左操作数指定的对象中。赋值表达式在赋值后具有左操作数的值,111) 但不是左值。赋值表达式的类型是左操作数在左值转换后的类型。更新左操作数的存储值的副作用是在左操作数和右操作数的值计算之后排序的。操作数的计算是无序的。

      现在的问题是,标准留下了两种方法如何获得赋值的值。**

      // variant 1 (read the left-hand side after writing it)
      list->tail = NULL;
      list->head = list->tail;
      

      也就是说,list->tail 在获得它的值后被读取,并将该值分配给list->head

      // variant 2 (use temporary storage)
      typeof(list->tail) temp = NULL;  // get the value/type of the RHS of the inner assignment
      list->tail = temp;
      list->head = temp;
      

      (RHS:右侧,运算符右侧的术语)。感谢@chux,这两个任务也可以互换。

      footnote 111:

      允许实现读取对象以确定值,但不是必需的,即使对象具有 volatile 限定类型


      变体在抽象机器方面表现相同 - 除非list->tail 是合格的volatile(更一般地说:除了最左边的对象之外的任何对象)。简而言之,volatile 告诉编译器对对象的访问有副作用。它通常用于外围硬件寄存器,例如USART。虽然它很少(如果经常被错误地)用于桌面应用程序,但通常用于操作系统内核驱动程序和裸机嵌入式系统。

      对此类寄存器的写入和读取通常会将写入(外部)硬件的数据分别传递。读取会产生这种硬件寄存器的值。更糟糕的是,这些寄存器可以是只写的或只读的。对于只写,读取会产生与写入内容无关的不确定值。与此相关的是,对这些对象的访问必须非常小心地控制和排序。不会有意外的读取或写入。

      因此,请考虑上面的代码。然后变体将生成对硬件的不同访问:

      • 变体 1 将导致先写入,然后再进行读取。如果寄存器是 只写,读取产生不相关的数据。这可能不是我们想要的。
      • 变体 2 只会写入,但会重用为第二个赋值写入的值。
      • 变体 2 的写入执行顺序不确定,这是 volatile 左值的另一个问题。

      请注意,使用哪种变体不是由用户决定的,而是由编译器决定的。它甚至可能因代码中的不同表达式而有所不同。

      因此,一旦涉及到volatile,链式分配就不再适用。

      【讨论】:

      • 请任何不了解差异的人在投票前发表评论以澄清!
      • 也许你可以澄清必须用 volatile 定义哪个对象才能解决这个问题,第一个或第二个成员或整个结构。 (我没有投反对票。)
      • @2501:好点,已编辑。希望你现在明白我的意思。 Re the DV: 是的,我觉得有人在我身后;得到了我大部分较新的答案 DVd。一定是个混蛋,要么不知道 C,理解我的答案(这就是我评论评论的原因),要么只是欺负。有些人不知道讨论中的开放词和学校操场之间的区别。
      • @chux:这会改变volatile左值的分配顺序。嗯,中间没有序列点,所以这将是嵌套分配的另一个问题。我会补充一点,不过它只会增加问题。
      • @Unlikely 不等于从不,是吗?你在(..)里说的原因也是有可能的。我知道这整件事非常迂腐。但是没有多少选择可以处理“技术上正确”和“我知道这在......时不起作用”之类的人。考虑到您提出的反对意见,我相信您会发现这种推理方式非常吸引人。
      猜你喜欢
      • 2016-10-09
      • 1970-01-01
      • 1970-01-01
      • 2011-01-08
      • 1970-01-01
      • 2013-02-26
      • 2020-12-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多