关于C++0x内存模型和序列点的一些思考

Several questions about sequence point and concurrent memory model. (mostly focused on N2052)

这几天断断续续一直在看关于C++0x的并发内存模型的proposal,这堆proposal显然是所有0x proposals里面最困难的。看的时候问题不断,单单是对原始单线程内存模型的改进和序列点概念的精化就非常tricky。所以看的时候总结了一些模糊的地方,昨天给发到comp.lang.c++.moderated上面去了,Francis Glassborow给了一个回贴澄清了里面的一个问题,不过尚留下不少其它的。相信后面还会有热心人(james kanze, maybe:))回复的,呵呵。

贴在这里,欢迎有兴趣的朋友一起探讨:-)

没时间细细转成中文的了,就直接把写的帖子贴在下面了,呵呵:)

[后记] 果然不出我所料,新闻组上的大牛人&大热心人James Kanze果然火速给出了最为详尽的回复,哈哈,我帖子里的绝大部分推测都是对的:) 有兴趣的朋友可以查看新闻组上的链接。


1. When the standard says that the order of evaluation of arguments to a function is unspecified, does that mean they could be overlapped, or just indeterminately ordered?

This problem occurs to me when I was reading Boehm's memory model proposal [N2052], in which he suggested using "sequenced before" instead of "sequence point" and then stipulated the evaluation order of function arguments as "unsequenced", which explicitly allows overlapping according to his proposal.

So, my question is, are those two constraints (i.e. "unsequenced evaluation order" and "unspecified evaluation order") essentially equal? 'cause I was thinking that "unsequenced" is looser than "unspecified evaluation order".

I couldn't find any statement about whether or not an unspecified order allows overlapping in C++98. So, Could any one give an explanation please?

P.S. According to another statement of the evaluation order, which says "there's an sequence point after the evaluation of all the arguments and before the entry of the function", this obviously means that the value computation and side effect of the evaluations of the arguments could take place in any order, even overlapped (?), because there's no sequence point between them, right? So the implementation is free to rearrange the instructions.

Plus, according to my understanding of the sequence point, it doesn't really matter whether or not the evaluations of several sub-expressions between two adjacent sequence points (may we now say "not sequently related"?) could be overlapped, because it would be just a specification of the semantics of the abstract machine anyway. So even if the standard says that they should not be overlapped, a particular implementation can make them overlapped anyway, because "If a side effect on a scalar object is not sequenced relative to either a different side effect on the same scalar object, or a value computation using the value of the same scalar object, the behavior is undefined. [Excerpt from N2052]" which guarantees that overlapped evaluation of the arguments doesn't really change anything.

The sentence quoted above, according to my understanding, is very important, it essentially says that we can view all the value computations between two adjacent sequence points as operating on immutable variables because otherwise the program will have undefined behavior (e.g. a store operation on some variable used by a value computation would break the rule). And now that they operate on immutable variables, we can execute them disorderly, unsequencedly, overlappingly, or whatever, right?

2. Another question of which I want to make sure my judgment is right; the following call has undefined behavior, right?

f(i,i++); // undefined behavior.

3. There's a somewhat confusing example in N2052, see below:

x++ + increment_x(); // Evaluation order unspecified; x may be incremented only once

How could x be incremented only once when there’re obviously two "++"s. The only way that can happen is one of the two "++"s is thrown away.

I guess what the example really wants to say is that "the value of x may be x+1 instead of x+2", since the compiler could rearrange the execution order like this:

#assuming x originally is 1

load x into some register say r1 ( here we have r1==1) // the first 1/3 of evaluating x++, the left operand of operator +

increment_x() // this makes the value of x 2, here we have completed the evaluation of the right operand of operator +

store r1+1 to the memory location of x // this makes the value of x r1+1, which is 2! not 3.

return r1 as the value computation of the left operand of "+" // we got 1

as a last step, the evaluation of "+" is done here, which gives a value of 2, which perhaps lots of people assume not.

While we are done, we can see that the value of x is 2, not 3!

So, while the exact words, I think, should be "x is incremented twice but the final value of x is the value of original x plus 1 so that it's SEEMINGLY incremented once", of course we could say x is incremented just once, because the result equals x+1, but that would be confusing and misleading, at least at first to me (coz I was all "how in the name of god is that going to happen?":-) ). So I suggest clarifying this example by giving more detailed explanation, after all it's a pretty important example, right?

And since we are on that, there's a tiny typo in the definition of increment_x():

int increment_x() { x++; } // here we dropped a "return".

Plus, should "x++ + increment_x();" have undefined behavior according to the rule: "If a side effect on a scalar object is not sequenced relative to either a different side effect on the same scalar object, or a value computation using the value of the same scalar object, the behavior is undefined"?

4. If C++98 means overlap-able when it says unspecified evaluation order, then why Boehm, in N1944, says below:

[Excerpt from N1944]

Not every side effect or evaluation can be authoritatively determined to be either previous to or subsequent to a given sequence point. For example, given

a = 0;

b = (a = 1, 2*a) + 3*a;

The evaluation of “3*a” is not ordered with respect to the sequence point introduced by the comma operator. It may be previous or subsequent (NOTE HERE!!); the standard simply doesn't say. Therefore, that evaluation may or may not be separated from the assignment by a sequence point.

[End]

Why is it "It may be previous or subsequent"? If unspecified evaluation means "could overlap", then this should really be "It may be previous or subsequent, or overlapped with the sequence point introduced by the comma op." When I say "overlapped with", I mean like:

load a into some register say r1

(a=1, 2*a)

plus r1 by 3

...

That means the evaluation of "3*a" is _splitted_ by the sequence point introduced by the comma op.

Is this ordering allowed by the current standard (C++98)? If it is, then would it conflict with 1.9p7:

....called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place?

5. from clause 5 paragraph 4:

"Between the previous and next sequence point a scalar object shall ... ... .The requirements of this paragraph shall be met for each allowable ordering of the sub-expressions of a full expression; otherwise the behavior is undefined."

Here, what are these "allowable ordering of the sub-expressions"? According to my recollection, in no place has the current standard clearly stated which ordering is allowable and which is not. "Unspecified" is not clear enough to me, especially when it comes to whether or not it allows overlapping, which kind of is the core of all the questions I brought up here.

6. If unsequenced evaluation(overlapped execution, in particular) is allowed as said in N2052, would the evaluation of some simple expression give unpredictable value, which in turn makes the behavior undefined? Here's an example:

double d = ... ;

double g(double& d){ return d*=3.12; }

f( ++d, g(d) ); // should this be undefined behavior?

Now let's just say that we're on such an architecture that storing a double consists of storing its lower and higher DWORDs( if sizeof(double) equals that of a qword ) __separately__ . (coz it seems I have qword-level store instruction on my x86 pc)

So, on such an architecture, the instructions generated for the call to 'f' above would possibly be like this:

load d and compute d+1

store the lower dword of the result into the lower dword memory location of d, which now makes d half-baked.

evaluate g(d), which will use the half-baked d to compute d*3.12, and store that into d, making d into a unreasonable/ridiculous state

store the higher dword of the result computed beforehand by "++d" into the high dword memory location of d, which makes the value of d even more ridiculous.

call f

So, it seems this clearly should be categorized as undefined behavior, isn't it? But the real question is, which rule in the standard state clearly that this is undefined behavior, or instead, which rule in N2052 did? Plus, if the behavior of this example is undefined, then the "x++ + increment_x(); " example in N2052 would have undefined behavior, too, right?

7. excerpt from N2052:

"Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function."

I think this is overconstrained, because it will make "++d" indeterminately sequenced with respect to "g(d)" in "f( ++d, g(d) );"(see the example above), while actually they're supposed to be unsequenced. (if "f( ++d, g(d) );" is not clear enough, how about "++d + g(d);" ?)

But I'm not quite sure about this, so correct me if I made any stupid mistake.

I need clear answers to these questions, every single one, please, because the memory model is really driving me crazy these days :-)

Again, any help would be greatly appreciated, thanks you guys!

P.S.新闻组链接见下面的回贴。

你可能感兴趣的:(C++,c,C#,F#,UP)