What are all the common undefined behaviours that a C++ programmer should know about?

C++UndefinedUndefined BehaviorC++ Faq

C++ Problem Overview


What are all the common undefined behaviours that a C++ programmer should know about?

Say, like:

a[i] = i++;

C++ Solutions


Solution 1 - C++

Pointer
  • Dereferencing a NULL pointer
  • Dereferencing a pointer returned by a "new" allocation of size zero
  • Using pointers to objects whose lifetime has ended (for instance, stack allocated objects or deleted objects)
  • Dereferencing a pointer that has not yet been definitely initialized
  • Performing pointer arithmetic that yields a result outside the boundaries (either above or below) of an array.
  • Dereferencing the pointer at a location beyond the end of an array.
  • Converting pointers to objects of incompatible types
  • Using memcpy to copy overlapping buffers.
Buffer overflows
  • Reading or writing to an object or array at an offset that is negative, or beyond the size of that object (stack/heap overflow)
Integer Overflows
  • Signed integer overflow
  • Evaluating an expression that is not mathematically defined
  • Left-shifting values by a negative amount (right shifts by negative amounts are implementation defined)
  • Shifting values by an amount greater than or equal to the number of bits in the number (e.g. int64_t i = 1; i <<= 72 is undefined)
Types, Cast and Const
  • Casting a numeric value into a value that can't be represented by the target type (either directly or via static_cast)
  • Using an automatic variable before it has been definitely assigned (e.g., int i; i++; cout << i;)
  • Using the value of any object of type other than volatile or sig_atomic_t at the receipt of a signal
  • Attempting to modify a string literal or any other const object during its lifetime
  • Concatenating a narrow with a wide string literal during preprocessing
Function and Template
  • Not returning a value from a value-returning function (directly or by flowing off from a try-block)
  • Multiple different definitions for the same entity (class, template, enumeration, inline function, static member function, etc.)
  • Infinite recursion in the instantiation of templates
  • Calling a function using different parameters or linkage to the parameters and linkage that the function is defined as using.
OOP
  • Cascading destructions of objects with static storage duration
  • 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
Source file and Preprocessing
  • A non-empty source file that doesn't end with a newline, or ends with a backslash (prior to C++11)
  • A backslash followed by a character that is not part of the specified escape codes in a character or string constant (this is implementation-defined in C++11).
  • Exceeding implementation limits (number of nested blocks, number of functions in a program, available stack space ...)
  • Preprocessor numeric values that can't be represented by a long int
  • Preprocessing directive on the left side of a function-like macro definition
  • Dynamically generating the defined token in a #if expression
To be classified
  • Calling exit during the destruction of a program with static storage duration

Solution 2 - C++

The order that function parameters are evaluated is unspecified behavior. (This won't make your program crash, explode, or order pizza... unlike undefined behavior.)

The only requirement is that all parameters must be fully evaluated before the function is called.


This:

// The simple obvious one.
callFunc(getA(),getB());

Can be equivalent to this:

int a = getA();
int b = getB();
callFunc(a,b);

Or this:

int b = getB();
int a = getA();
callFunc(a,b);

It can be either; it's up to the compiler. The result can matter, depending on the side effects.

Solution 3 - C++

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.

Solution 4 - C++

Assigning to a constant after stripping constness using const_cast<>:

const int i = 10; 
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined

Solution 5 - C++

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.

Solution 6 - C++

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

Solution 7 - C++

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.

Solution 8 - C++

A basic understanding of the various environmental limits. The full list is in section 5.2.4.1 of the C specification. Here are a few;

  • 127 parameters in one function definition
  • 127 arguments in one function call
  • 127 parameters in one macro definition
  • 127 arguments in one macro invocation
  • 4095 characters in a logical source line
  • 4095 characters in a character string literal or wide string literal (after concatenation)
  • 65535 bytes in an object (in a hosted environment only)
  • 15nesting levels for #includedfiles
  • 1023 case labels for a switch statement (excluding those for anynested switch statements)

I was actually a bit surprised at the limit of 1023 case labels for a switch statement, I can forsee that being exceeded for generated code/lex/parsers fairly easially.

If these limits are exceeded, you have undefined behavior (crashes, security flaws, etc...).

Right, I know this is from the C specification, but C++ shares these basic supports.

Solution 9 - C++

The only type for which C++ guarantees a size is char. And the size is 1. The size of all other types is platform dependent.

Solution 10 - C++

Namespace-level objects in a different compilation units should never depend on each other for initialization, because their initialization order is undefined.

Solution 11 - C++

Using memcpy to copy between overlapping memory regions. For example:

char a[256] = {};
memcpy(a, a, sizeof(a));

The behavior is undefined according to the C Standard, which is subsumed by the C++03 Standard.

7.21.2.1 The memcpy function

> Synopsis

> 1/ #include void *memcpy(void * restrict s1, const > void * restrict s2, size_t n);

> Description

> 2/ The memcpy function > copies n characters from the object pointed to by s2 into the object > pointed to by s1. If copying takes place between objects that overlap, > the behavior is undefined. Returns 3 The memcpy function returns the > value of s1.

7.21.2.2 The memmove function

> Synopsis > > 1 #include void *memmove(void *s1, const void *s2, size_t > n); > > Description > > 2 The memmove function copies n characters from the object pointed to > by s2 into the object pointed to by s1. Copying takes place as if the > n characters from the object pointed to by s2 are first copied into a > temporary array of n characters that does not overlap the objects > pointed to by s1 and s2, and then the n characters from the temporary > array are copied into the object pointed to by s1. Returns > > 3 The memmove function returns the value of s1.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionyesraajView Question on Stackoverflow
Solution 1 - C++Diomidis SpinellisView Answer on Stackoverflow
Solution 2 - C++Martin YorkView Answer on Stackoverflow
Solution 3 - C++Martin YorkView Answer on Stackoverflow
Solution 4 - C++yesraajView Answer on Stackoverflow
Solution 5 - C++Daniel EarwickerView Answer on Stackoverflow
Solution 6 - C++ConstantinView Answer on Stackoverflow
Solution 7 - C++Martin YorkView Answer on Stackoverflow
Solution 8 - C++RandomNickName42View Answer on Stackoverflow
Solution 9 - C++JaredParView Answer on Stackoverflow
Solution 10 - C++yesraajView Answer on Stackoverflow
Solution 11 - C++John DiblingView Answer on Stackoverflow