The "sequence point" description helps in determining what is well
defined in C (and C++).
For example:
int f()
{
int i = 0;
int j = 0;
return g( ++i, ++j ); // Assume g() is pure function of two ints.
}
is well defined, as i and j are both incremented between the sequence
point at the semicolon after the initialization of j and the sequence
point at the entry to g(), but the order is unspecified (and
unknowable).
Ironically, if i and j were declared as volatile, the code becomes
undefined because the order of the writes to i and j become part of the
(potentially) observable behavior of the program.
The compiler is allowed to perform any rewriting of the code that has
the same observable behavior as the written code. Without any volatile
variables, the observable behavior of, say,
std::cout << f() << std::endl;
can be rewritten by the compiler as:
std::cout << g(1, 1) << std::endl;
even if f() is not defined to be inline. The compiler can keep
rewriting as long as desired, so if g was the extremely recursive
Ackerman function (
http://en.wikipedia.org/wiki/Ackermann_function),
the code could be rewritten as:
std::cout << 3 << std::endl;
The compiler can also optimise the resulting machine operations provide
the observable behavior is unchanged. In other words a well-defined
program must act "as if" the code was executed exactly as written.
In the original question, Grislyk asked if volatile was needed to
prevent "unused" objects being removed. The rules of C++ do say that
volatile is needed to ensure that they are actually constructed and
destructed. In practice, this is not an issue, as C++ cannot optimize
away observable behaviour.
In the code:
class A{ A(){cout<<"start..."; } }
class B{ B(){cout<<"ok"<<endl; } }
void inp();
void test()
{
A();
inp();
B();
}
test() may be implimented directly something like this (don't take too
literally):
void test()
{
{
char tmp[sizeof(A)]; // Allocate memory.
new( tmp ) A; // Initialize memory by calling A::A(),
writes "start..."
(A *)(tmp)->~A(); // Destruct A (no-op)
}
inp();
B(); // Same as for A above.
}
A smart compiler will follow the "as if" rule and produce code
equivalent to this:
void test()
{
cout<<"start..."; // Only observable behavior in A::A()
inp();
cout<<"ok"<<endl;
}
In nearly all real situations, this is exactly what was intended in the
first place, actually allocating and constructing A and B being
incidental to the requirements.
Best Regards,
David O.