liveBook · Manning

Undefined behavior

As explained in 100 C++ Mistakes and How to Avoid Them

Undefined behavior is a program operation whose result or behavior is not defined by the applicable language standard.

Undefined behavior is an operation whose result or behavior the applicable language standard does not define. The compiler may produce unpredictable results, crashes, security vulnerabilities, or remove the operation during optimization. section 1.2.1

Treat undefined behavior as a correctness failure, even when a program appears to work. Common causes include reading uninitialized data, deleting a derived object through a base pointer without a virtual destructor, and returning a pointer or reference to a local object. section 8.11 section 8.8 section 11.4

Prevent undefined behavior by making object state and lifetime explicit:

  • Initialize every built-in member before its first use; make constructors establish the class invariant rather than relying on later setter calls. section 8.11 section 7.3
  • Require meaningful constructor arguments and validate them during initialization, such as rejecting a negative age. section 8.11 section 7.3
  • Give a polymorphic base class a virtual destructor before deleting derived objects through base pointers. section 8.8
  • Return locally computed objects by value instead of exposing pointers or references whose storage ends when the function returns. section 11.4

A lifetime error that looks successful

A catenate function that returns a reference to its local combined string may print the expected message if the memory remains untouched, but accessing that reference is unsafe after the function ends. Returning std::string by value produces a correct result; modern compilers may minimize copying through return value optimization. section 11.4

Listing 8.21 A class with an uninitialized built-in instance variable
class Person {
private:
    std::string name;                                         #1
    int age;                                                  #2
public:
    Person(const std::string& name) { this->name = name; }    #3
    void setAge(int age) { this->age = age; }
    friend std::ostream& operator<<(std::ostream&, const Person&);
};
 
std::ostream& operator<<(std::ostream& o, const Person& p) {
    o << p.name << " is " << p.age << " years old";
    return o;
}
 
int main() {
    Person joey("Joey");
    std::cout << joey << '\n';
    return 0;
}

Inspect the Person example to see how an uninitialized built-in member can survive compilation and appear to work until it is read. section 8.11

How 100 C++ Mistakes and How to Avoid Them covers it

How deep it goes
Defines undefined behavior and explains several object-lifetime causes, but stops short of a complete language-standard taxonomy.
Examples
Uses uninitialized Person members, base-pointer deletion of Square, and returning a local concatenated string.
What it assumes
Assumes basic C++ classes, constructors, pointers, references, inheritance, and function returns while teaching their lifetime risks through examples.
What you can do after
Diagnose/debug with it by recognizing unsafe reads, polymorphic deletion, and dangling returns, then apply initialization, virtual destructors, or value returns.
How it connects
Connects undefined behavior with language standards, compiler optimization, constructors, class invariants, polymorphism, destructors, and return value optimization.

Also explained in

Compare how 7 books explain undefined behavior →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 C++ Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.