重新加载未定义行为和序列点

请将本主题视为下列主题的续集:

上一期
未定义行为和序列点

让我们回顾一下这个 有意思很复杂表达式(斜体的短语取自上面的主题 * smile *) :

i += ++i;

我们说这会引起未定义的行为。我假设当我们这样说时,我们隐式地假设 i类型是内置类型之一。

如果 i类型是用户定义的类型,该怎么办?假设它的类型是 Index,这是在本文后面定义的(见下文)。它还会调用未定义的行为吗?

如果是,为什么?它难道不等同于编写 i.operator+=(i.operator++());或者甚至语法上更简单的 i.add(i.inc());吗?或者,它们也会调用未定义的行为吗?

如果没有,为什么不呢?毕竟,对象 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;
}
};
12975 次浏览

It looks like the code

i.operator+=(i.operator ++());

Works perfectly fine with regards to sequence points. Section 1.9.17 of the C++ ISO standard says this about sequence points and function evaluation:

When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function.

This would indicate, for example, that the i.operator ++() as the parameter to operator += has a sequence point after its evaluation. In short, because overloaded operators are functions, the normal sequencing rules apply.

Great question, by the way! I really like how you're forcing me to understand all the nuances of a language that I already thought I knew (and thought that I thought that I knew). :-)

I think it's well-defined:

From the C++ draft standard (n1905) §1.9/16:

"There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function13) . Several contexts in C++ cause evaluation of a function call, even though no corresponding function call syntax appears in the translation unit. [ Example: evaluation of a new expression invokes one or more allocation and constructor functions; see 5.3.4. For another example, invocation of a conversion function (12.3.2) can arise in contexts in which no function call syntax appears. — end example ] The sequence points at function-entry and function-exit (as described above) are features of the function calls as evaluated, whatever the syntax of the expression that calls the function might be. "

Note the part I bolded. This means there is indeed a sequence point after the increment function call (i.operator ++()) but before the compound assignment call (i.operator+=).

As others have said, your i += ++i example works with the user-defined type since you're calling functions, and functions comprise sequence points.

On the other hand, a[++i] = i is not so lucky assuming that a is your basic array type, or even a user defined one. The problem you've got here is that we don't know which part of the expression containing i is evaluated first. It could be that ++i is evaluated, passed off to operator[] (or the raw version) in order to retrieve the object there, and then the value of i gets passed to that (which is after i was incremented). On the other hand, perhaps the latter side is evaluated first, stored for later assignment, and then the ++i part is evaluated.

Alright. After going through previous responses, I re-thought about my own question, particularly this part that only Noah attempted to answer but I'm not convinced with him completely.

a[++i] = i;

Case 1:

If a is an array of built-in type. Then what Noah said is correct. That is,

a[++i] = i is not so lucky assuming that a is your basic array type, or even a user defined one . The problem you've got here is that we don't know which part of the expression containing i is evaluated first.

So a[++i]=i invokes undefined-behavior, or the result is unspecified. Whatever it is, it's not well-defined!

PS: In above quotation, strike-through is of course mine.

Case 2:

If a is an object of user-defined type which overloads the operator[], then again there are two cases.

  1. If the return type of overloaded operator[] function is built-in type, then again a[++i]=i invokes undefined-behavior or the result is unspecified.
  2. But if the return type of overloaded operator[] function is user-defined type, then the behavior of a[++i] = i is well-defined (as far as I understand), since in this case a[++i]=i is equivalent to writing a.operator[](++i).operator=(i); which is same as, a[++i].operator=(i);. That is, assignment operator= gets invoked on the a[++i] = i3 object of a[++i], which seems be very well-defined, since by the time a[++i] returns, ++i have already been evaluated, and then the a[++i] = i4 object calls operator= function passing the updated value of a[++i] = i0 to it as argument. a[++i] = i5. And the syntax ensures that there is no competition between these two calls, and operator[] would get invoked first, and consecutively, the argument ++i passed into it, would also get evaluated first.

Think of this as someInstance.Fun(++k).Gun(10).Sun(k).Tun(); in which each consecutive function call returns an object of some user-defined type. To me, this situation seems more like this: eat(++k);drink(10);sleep(k), because in both situations, there exists sequence point after each function call.

Please correct me if I'm wrong. :-)

http://www.eelis.net/C++/analogliterals.xhtml Analog literals comes to my mind

  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 );