【问题标题】:Does the C99 standard permit assignment of a variable to itself?C99 标准是否允许将变量分配给自身?
【发布时间】:2013-07-17 15:20:52
【问题描述】:

C99 标准是否允许将变量分配给自己?例如,以下是否有效:

int a = 42;

/* Case 1 */
a = a;

/* Case 2 */
int *b = &a;
a = *b;

虽然我怀疑案例 1 是有效的,但对于案例 2,我很犹豫。

在赋值的情况下,右侧是否在将值分配给左侧的变量之前完全评估了 - 或者是在取消引用指向被分配变量的指针时引入的竞争条件?

【问题讨论】:

  • a = a; 是高级语言中的死指令,我很确定优化阶段会删除它
  • @GrijeshChauhan 如果 a 在内存中并且不需要优化,则并非总是如此。请参阅下面的示例。
  • 只要变量已经正确初始化,应该没问题。如果变量未初始化,则复制未初始化的值不会使其更好地初始化。如果变量是volatile,它可能不是空操作(编译器有义务读取该值并写入再次读回的值)。当变量不符合volatile 时,它应该是空操作。

标签: c pointers c99 language-lawyer


【解决方案1】:

这两种情况都完全有效,因为a 的值仅用于确定要存储的值,而不是确定要存储该值的对象。

本质上,在一项作业中,您必须区分三种不同的操作

  • 确定要存储值的对象
  • 评估 RHS
  • 将确定的值存储在确定的对象中

这三个操作中的前两个可以按任意顺序进行,甚至可以并行进行。第三个显然是其他两个的结果,所以它会在之后。

【讨论】:

    【解决方案2】:

    这是完全有效的,您只是使用先前的值来确定要存储的值。 draft C99 standard 部分 6.5.2 对此进行了介绍:

    在前一个序列点和下一个序列点之间,一个对象应该有它的 存储的值最多修改一次由评估 表达式。此外,先验值应只读为 确定要存储的值。

    有效代码示例如下:

    i = i + 1;
    

    C 和 C++ 部分 here 涵盖了可能出现序列点的不同位置。

    【讨论】:

      【解决方案3】:

      C99 6.5.16.1 简单赋值

      3 如果存储在一个对象中的值是从另一个以任何方式重叠的对象中读取的 存储第一个对象,则重叠应准确,两个对象应 具有兼容类型的合格或不合格版本;否则,行为是 未定义。

      我认为示例代码符合“重叠”条件。由于它们确实具有兼容类型的合格版本,因此结果是有效的。

      还有 6.5.16 赋值运算符

      4 操作数的求值顺序未指定。如果尝试修改 赋值运算符的结果或在下一个序列点之后访问它, 行为未定义。

      仍然没有“尝试修改结果”,所以结果是有效的。

      【讨论】:

      • 关于第 4 段 (If an attempt is made to modify the result of an assignment operator or to access it after the next sequence point, the behavior is undefined.),由于a 在评估之前已被修改:a = ++a,因此这是否是未定义的?什么是序列点
      • @VilhelmGray 这是关于序列点的一个很好的参考:en.wikipedia.org/wiki/Sequence_point 它有一个关于 C 和 C++ 的部分以及什么是序列点。
      • @VilhelmGray 是的,没错,幻灯片 197 有这个例子:slideshare.net/olvemaudal/deep-c,这个页面还有更多这样的例子:securecoding.cert.org/confluence/display/seccode/…
      • @VilhelmGray 您之前询问过c = ++a + a,但您的两个示例都未定义。
      • @VilhelmGray;在表达式 `c = ++(++a) 的情况下,后增量 (a++) 的结果不是左值,即不能再次后增量。看到这个link
      【解决方案4】:

      假设编译器没有通过简单地删除第一条指令来优化它,这里甚至存在竞争条件。在大多数架构上,如果 a 存储在内存中 a = a 将被编译为两个移动指令(mem => reg,reg => mem),因此不是原子的。

      这是一个例子:

      int a = 1;
      int main() 
      { a = a; }
      

      在带有 gcc 4.7.1 的 Intel x86_64 上的结果

      4004f0:       8b 05 22 0b 20 00       mov    0x200b22(%rip),%eax        # 601018 <a>
      4004f6:       89 05 1c 0b 20 00       mov    %eax,0x200b1c(%rip)        # 601018 <a>
      

      【讨论】:

      • 但你是假设,你试过了吗?为什么没有任何编译器不删除它?很容易检测。是你的真实例子吗??哪个编译器?
      • 虽然@GrijeshChauhan 可能是正确的,现代编译器很可能不会这样做,但我对 C99 标准是否允许这种行为感兴趣——毕竟,编译器不必是 实用符合标准
      • hmm hivert 提出了如果编译器不优化它会是什么样子,所以它是一个很好的答案。
      【解决方案5】:

      我看不到 C 编译器不允许允许a = a。这种分配可能由于程序员不知道的宏而偶然发生。它甚至可能不会生成任何代码,因为这是一个优化问题。

      #define FOO (a)
      ...
      a = FOO;
      

      示例代码很容易编译,我对 C 标准的审查表明没有禁止。

      关于竞态条件@Yu Hao 回答得很好:没有竞态条件。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-03-27
        • 2011-09-17
        • 2015-10-03
        • 1970-01-01
        • 2021-11-01
        • 1970-01-01
        • 2011-03-27
        相关资源
        最近更新 更多