提问人:Nawaz 提问时间:1/9/2011 最后编辑:CommunityNawaz 更新时间:1/20/2021 访问量:13318
未定义的行为和重新加载的序列点
Undefined behavior and sequence points reloaded
问:
将本主题视为以下主题的续集:
上一期
未定义的行为和序列点
让我们重温一下这个有趣而复杂的表达方式(斜体短语取自上面的主题*微笑*):
i += ++i;
我们说这调用了未定义的行为。我假设,当这样说时,我们隐含地假设 type of 是内置类型之一。i
如果类型是用户定义的类型,该怎么办?假设它的类型是本文后面定义的类型(见下文)。它是否仍会调用未定义的行为?i
Index
如果是,为什么?难道它不等同于写作,甚至在语法上更简单吗?或者,它们是否也调用了未定义的行为?i.operator+=(i.operator++());
i.add(i.inc());
如果不是,为什么不呢?毕竟,对象在连续的序列点之间被修改了两次。请记住经验法则:表达式只能在连续的“序列点”之间修改对象的值一次。如果是一个表达式,那么它必须调用 undefined-behavior。如果是这样,那么它的等价物也必须调用似乎不真实的未定义行为!(据我了解)i
i += ++i
i.operator+=(i.operator++());
i.add(i.inc());
或者,一开始就不是一个表达式吗?如果是这样,那么它是什么,表达的定义是什么?i += ++i
如果它是一个表达式,同时它的行为也是明确定义的,那么它意味着与表达式关联的序列点数在某种程度上取决于表达式中涉及的操作数的类型。我是否正确(甚至部分正确)?
顺便问一下,这个表情怎么样?
//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!
a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.
你也必须在你的回应中考虑这一点(如果你肯定知道它的行为)。:-)
是
++++++i;
在 C++03 中定义良好?毕竟,这是这个,
((i.operator++()).operator++()).operator++();
class Index
{
int state;
public:
Index(int s) : state(s) {}
Index& operator++()
{
state++;
return *this;
}
Index& operator+=(const Index & index)
{
state+= index.state;
return *this;
}
operator int()
{
return state;
}
Index & add(const Index & index)
{
state += index.state;
return *this;
}
Index & inc()
{
state++;
return *this;
}
};
答:
它看起来像代码
i.operator+=(i.operator ++());
在序列点方面工作得很好。C++ ISO 标准的第 1.9.17 节对序列点和函数计算是这样说的:
调用函数时(无论函数是否内联),在函数体中执行任何表达式或语句之前,在计算所有函数参数(如果有)之后都会有一个序列点。在复制返回值之后和执行函数外部的任何表达式之前,还有一个序列点。
例如,这将指示 as 参数 to 在其计算后有一个序列点。简而言之,由于重载运算符是函数,因此适用正常的排序规则。i.operator ++()
operator +=
顺便说一句,好问题!我真的很喜欢你强迫我理解一种语言的所有细微差别,我已经认为我知道(并认为我认为我知道)。:-)
我认为它的定义很明确:
摘自 C++ 标准草案 (n1905) §1.9/16:
“之后还有一个序列点 复制返回值和 在执行任何之前 函数外部的表达式13)。 C++ 中的多个上下文导致 函数调用的评估,甚至 虽然没有相应的函数调用 语法出现在翻译中 单位。[ 示例:评估新的 expression 调用一个或多个 分配和构造函数; 参见 5.3.4。再举一个例子, 转换函数的调用 (12.3.2) 可能出现在 未显示函数调用语法。 — 结束示例 ] 序列指向 function-entry 和 function-exit(如 如上所述)是 函数调用已评估,无论 表达式的语法 调用函数可能是。"
请注意我加粗的部分。这意味着在增量函数调用 () 之后、复合赋值调用 () 之前确实存在一个序列点。i.operator ++()
i.operator+=
正如其他人所说,您的示例使用用户定义类型,因为您正在调用函数,而函数由序列点组成。i += ++i
另一方面,假设这是您的基本数组类型,甚至是用户定义的数组类型,那就不那么幸运了。您在这里遇到的问题是,我们不知道首先计算包含的表达式的哪一部分。它可能是被评估的,传递给(或原始版本)以便在那里检索对象,然后将 的值传递给该对象(之后是递增的)。另一方面,也许先评估后一面,存储以备以后分配,然后评估该部分。a[++i] = i
a
i
++i
operator[]
i
i
++i
评论
++i
i
a
i
好。在浏览了之前的回答之后,我重新思考了我自己的问题,尤其是只有诺亚试图回答的这一部分,但我并不完全相信他。
a[++i] = i;
案例一:
If 是内置类型的数组。那么挪亚说的是对的。那是a
a[++i] = 假设我没有那么幸运 a 是您的基本数组类型,
或者 甚至用户定义了一个.问题 你在这里是我们不知道的 表达式的哪一部分 首先评估包含 I。
因此调用未定义的行为,或者结果未指定。不管它是什么,它都没有明确的定义!a[++i]=i
PS:在上面的引文中,删除线当然是我的。
案例二:
如果是重载 的用户定义类型的对象,则同样有两种情况。a
operator[]
- 如果重载函数的返回类型是内置类型,则再次调用 undefined-behavior 或未指定结果。
operator[]
a[++i]=i
- 但是,如果重载函数的返回类型是用户定义的类型,那么 的行为是明确定义的(据我所知),因为在这种情况下等价于编写与 相同的 。也就是说,在返回的对象上调用赋值,这似乎定义得非常好,因为到返回时已经计算过了,然后返回的对象调用函数,将更新的值作为参数传递给它。请注意,这两个调用之间有一个序列点。语法确保这两个调用之间没有竞争,并且会首先被调用,然后,传递给它的参数也会首先被评估。
operator[]
a[++i] = i
a[++i]=i
a.operator[](++i).operator=(i);
a[++i].operator=(i);
operator=
a[++i]
a[++i]
++i
operator=
i
operator[]
++i
可以将其视为每个连续的函数调用返回某个用户定义类型的对象。在我看来,这种情况更像是这样:,因为在这两种情况下,每次函数调用后都存在序列点。someInstance.Fun(++k).Gun(10).Sun(k).Tun();
eat(++k);drink(10);sleep(k)
如果我错了,请纠正我。:-)
评论
k++
k
Sun
Fun
Fun
Sun
Fun
Sun
Sun
Fun
++k
Fun
Sun
k
k
++k
Sun
Fun
k
++k
eat(i++);drink(10);sleep(i);
i++
k
++k
i
i++
eat()
sleep()
http://www.eelis.net/C++/analogliterals.xhtml我想起了模拟文字
unsigned int c = ( o-----o
| !
! !
! !
o-----o ).area;
assert( c == (I-----I) * (I-------I) );
assert( ( o-----o
| !
! !
! !
! !
o-----o ).area == ( o---------o
| !
! !
o---------o ).area );
评论
s