Say like a[i] = i++;
views:
3330answers:
13The only type for which C++ guarantees a size is char. And the size is 1. The size of all other types is platform dependent
By going through the C++ standard, I found that the following actions will yield undefined behavior. My list does not include the use of the C++ standard library.
- Dereferencing a NULL pointer
- Dereferencing a pointer returned by a request for a zero-size object
- Using pointers to objects whose lifetime has ended (for instance, stack allocated objects or deleted objects)
- Depending on the value of an uninitialized automatic variable
- A zero second operand to integer / and % operators
- Performing pointer arithmetic that yields a result outside the boundaries (+1) of an array.
- Dereferencing the pointer to (after) the end of an array.
- Converting a floating point value to a value that can't be represented by the target type
- Evaluating an expression whose result is not in the range of the corresponding types
- Converting pointers to objects of incompatible types
- Attempting to modify a string literal or other const object during its lifetime
- Not returning a value from a value-returning function (directly or by flowing off from a try-block)
- The value of any object of type other than volatile or sig_atomic_t at the receipt of a signal
- A non-empty file that doesn't end with a newline, or ends with a backslash
- Preprocessor numeric values that can't be represented by a long int
- A backslash followed by a character that is not part of the specified escape codes in a character or string constant.
- Concatenating a narrow with a wide string literal during preprocessing
- Multiple different definitions for the same entity (class, template, enumeration, inline function, static member function, etc.)
- Calling exit during the destruction of a program with static storage duration
- Cascading destructions of objects with static storage duration
- Shifting values by a negative amount
- The result of assigning to partially overlapping objects
- Recursively re-entering a function during the initialization of its static objects
- Making virtual function calls to pure virtual functions of an object from its constructor or destructor
- Referring to nonstatic members of objects that have not been constructed or have already been destructed
- Infinite recursion in the instantiation of templates
- Dynamically generating the defined token in a #if expression
- Preprocessing directive on the left side of a function-line macro definition
The order that function parameters are evaluated is unspecified.
The only requirement is that all parameters must be fully evaluated before the function is called.
// The simple obvious one.
callFunc(getA(),getB());
// Is this
int a = getA();
int b = getB();
callFunc(a,b);
// or
int b = getB();
int a = getA();
callFunc(a,b);
// The answer is either. Up to the compiler.
// The answer can matter depending on the side effects.
Variables may only be updated once in an expression
(Technically once between sequence points).
int i =1;
i = ++i;
// Undefined. Assignment to i twice in the same expression.
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.
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
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.
The evaluation order of function parameters is arbitrary. (So do not place operations between brackets in a function call)
Besides undefined behaviour there is also 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 this question.
Implementation-defined behaviour only bites you when you start porting (but upgrading to new version of compiler is also porting!)
Namespace-level objects in different compilation units should never depend on each other for initialization, because their initialization order is undefined