【问题标题】:Pre-increment operator returns lvalue or rvalue? [duplicate]预增量运算符返回左值还是右值? [复制]
【发布时间】:2014-03-17 21:38:49
【问题描述】:

在这里查看其他问题,我发现 C 中的预增量运算符返回右值,而不是左值。但是,在尝试下面的代码时

int a=35;
printf("%d %d %d %d %d",a++,a,++a,a++,++a);

我期待输出

38 39 38 36 36

但我得到了输出

38 39 39 36 39

如果pre-increment返回rvalue,那么为什么打印'a'的最终值(意味着返回lvalue)而不是执行增量后立即返回的值?

【问题讨论】:

  • 上帝这个问题应该回答多少次?这是一种未定义的行为
  • 另外,这与右值和左值无关。简而言之,左值是您可以分配的值,右值是您无法分配的值。你不能写++a=5,因为这是一个右值。您将左值和右值与从左到右或从右到左的关联性混淆了。
  • @AswinMurugesh 但是,printf() 不会将参数推入堆栈吗?那样的话,应该定义评估的顺序??
  • “我假设左值被返回,因为当我试图追踪评估顺序时”:同样,右值/左值与评估顺序无关。这并不意味着左或右。

标签: c lvalue rvalue pre-increment


【解决方案1】:

您正在调用未定义的行为:分隔函数调用参数的逗号不是“逗号运算符”。与它不同的是,它们不是序列点。

此外,您的示例与运算符返回右值还是左值无关,因为您的代码不会尝试修改返回的值。

【讨论】:

  • 自 C 2011 以来,C 已将这种未定义的行为基于两个副作用或副作用和值计算是否未排序(5.1.2.3 3 和 6.5 2),不是关于是否存在中间序列点。 (中间的序列点仍然足够,但不是必需的。)
  • @EricPostpischil 您似乎暗示可以证明 sequence point 和先排序(/after/unsequenced 等)可以相互独立?更具体地说,这句话暗示了什么:中间的序列点仍然足够,但不是必需的?你有相反的例子吗?即两个表达式A和B之间存在严格的偏序关系,但是否存在序列点无法证明。
  • (首先,更正:如果没有附加条件,我关于序列点足够的断言可能是错误的。中间序列点的存在并不总是足以确定顺序,因为有些表达式序列点必须出现在两件事之间,即使它没有指定哪个是第一个。)是的,有些表达式的序列不涉及序列点。一种是运算符的操作数的值计算在运算符结果的值计算之前排序(6.5 1)。
【解决方案2】:

当你写一个函数调用比如printf("%d %d %d %d %d",a++,a,++a,a++,++a);时,它变成了一组要执行的操作:

  • 第一个参数的计算结果为指向字符串的指针。
  • 第二个参数导致两个操作:产生a 的值。将a 设置为a 的值加一。
  • 第三个参数的计算结果为 a
  • 第四个参数导致两个操作:产生比a 的值多一个。将a 设置为a 的值加一。
  • 第五个参数导致两个操作:产生a的值。将a 设置为a 的值加一。
  • 第六个参数导致两个操作:产生比a 的值多一个。将a 设置为a 的值加一。
  • 函数调用将参数传递给函数并调用它。

C 实现必须执行所有这些操作。但是,C 标准并未完全指定它们的执行顺序。根据 C 2011 (N1570) 6.5.2.3 10,在参数评估之后和调用之前有一个 序列点。这意味着必须在执行调用之前执行由参数本身产生的操作。

但是,C 标准没有说明参数的评估顺序。它甚至没有说由单个表达式产生的两个操作(如a++)必须一起执行。允许 C 实现准备 a 的值,然后评估其他一些参数,然后将 a 设置为 a 的值加一。

这意味着您的函数调用可能会以任意数量的不同顺序多次使用a 的值并设置a 的值。这是一个问题,因为它违反了 C 标准中的规则。 6.5 2 说:

如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用同一标量对象的值的值计算是未排序的,则行为未定义......

在本文中,“未排序”表示两个操作没有 C 标准设置的明确顺序:如果允许 C 实现以任一顺序执行两个操作,则它们是未排序的。您的printf 调用包含几个对a 有副作用的未排序操作(设置aa++ 的副作用)并且计算一个值(只需使用a 传递一个参数“计算”它值)。

因此,您的printf 调用违反了 C 标准的规则,导致的行为未定义。

您编写的每个普通表达式都应该只修改任何对象一次。不要在同一个表达式中使用两次a++。此外,如果您修改对象,则不能单独使用它。如果您在表达式中的一个位置有a++,则不能在另一个位置单独使用a

一些特殊运算符有例外。 &&|| 运算符需要首先计算其左操作数。这会导致左侧的子表达式在右侧的子表达式之前排序,从而避免违反 C 2011 6.5 2 中的规则。因此a++ && a++ 是合法的。逗号运算符还首先计算左侧的子表达式,因此 a++, ++a, a 是合法的。但是,函数调用的参数是参数的列表,而不是逗号运算符的使用。

【讨论】:

    猜你喜欢
    • 2021-09-26
    • 2011-01-03
    • 2019-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 1970-01-01
    相关资源
    最近更新 更多