Besides undefined behaviour, there is also the equally nasty implementation-defined behaviour.
Undefined behaviour occurs when a program does something the result of which is not specified by the standard.
Implementation-defined behaviour is an action by a program the result of which is not defined by the standard, but which the implementation is required to document. An example is "Multibyte character literals", from Stack Overflow question Is there a C compiler that fails to compile this?.
Implementation-defined behaviour only bites you when you start porting (but upgrading to new version of compiler is also porting!)
The compiler is free to re-order the evaluation parts of an expression (assuming the meaning is unchanged).
From the original question:
a[i] = i++;
// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)
// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:
int rhs = i++;
int lhs& = a[i];
lhs = rhs;
// or
int lhs& = a[i];
int rhs = i++;
lhs = rhs;
Double Checked locking.
And one easy mistake to make.
A* a = new A("plop");
// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'
// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.
// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
a = new A("Plop"); // (Point A).
}
}
a->doStuff();
// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
// Remember (c) has been done thus 'a' is not NULL.
// But the memory has not been initialized.
// Thread 2 now executes doStuff() on an uninitialized variable.
// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
A* tmp = new A("Plop"); // (Point A).
a = tmp;
}
}
a->doStuff();
// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.
My favourite is "Infinite recursion in the instantiation of templates" because I believe it's the only one where the undefined behaviour occurs at compile time.
Namespace-level objects in a different compilation units should never depend on each other for initialization, because their initialization order is undefined.
Besides undefined behaviour, there is also the equally nasty implementation-defined behaviour.
Undefined behaviour occurs when a program does something the result of which is not specified by the standard.
Implementation-defined behaviour is an action by a program the result of which is not defined by the standard, but which the implementation is required to document. An example is "Multibyte character literals", from Stack Overflow question Is there a C compiler that fails to compile this?.
Implementation-defined behaviour only bites you when you start porting (but upgrading to new version of compiler is also porting!)
The compiler is free to re-order the evaluation parts of an expression (assuming the meaning is unchanged).
From the original question:
Double Checked locking. And one easy mistake to make.
My favourite is "Infinite recursion in the instantiation of templates" because I believe it's the only one where the undefined behaviour occurs at compile time.
Namespace-level objects in a different compilation units should never depend on each other for initialization, because their initialization order is undefined.
Assigning to a constant after stripping
const
ness usingconst_cast<>
: