Undefined Behavior in C++

Course 3 · Ch 5
Undefined Behavior in C++
c3-4's own catalog, inherited unchanged — plus what's genuinely new once classes and virtual dispatch enter the picture

This chapter doesn't re-derive c3-4's own UB deep dive — everything there still applies, unchanged. This is only what's genuinely new to C++ specifically.

Everything From c3-4 Still Applies

Signed overflow, out-of-bounds access, dangling pointers, use-after-free, double-free, strict aliasing, excessive shifts, data races — every category c3-4 catalogued applies unchanged, since C++ inherits C's entire UB model at the language-core level.

Object Slicing

A genuinely new-to-C++ trap: assigning a derived-class object to a base-class object by value — not through a pointer or reference — "slices off" the derived part entirely, leaving only the base portion. Not technically undefined behavior in the strict sense (copying just the base part is well-defined), but almost always a serious logic bug, since cpp1-7's own virtual dispatch is lost entirely.

Circle c(5.0); Shape s = c; // SLICED — s is now just a plain Shape, radius and Circle's own area() are gone s.area(); // calls Shape::area(), never Circle::area() — silently

This is exactly what cpp2-6's own "catch by reference to avoid slicing" tip was pointing toward — now fully explained.

Pure Virtual Function Called From a Constructor

A genuinely real, subtle trap: calling a virtual function from a base class's own constructor does not dispatch to a derived override, even if the object being constructed is ultimately a derived type. Calling a pure virtual function this way is genuinely undefined behavior, not merely surprising.

Why Construction Order Causes This

During a base class's own constructor, the object's hidden vtable pointer is set to point at the base class's vtable — not the derived one. It's updated to point at successively more-derived vtables as each level of construction completes, finishing only once the most-derived constructor runs. Calling a virtual function mid-construction sees whatever vtable is currently set, never the final one.

Uninitialized Member Variables

C++ class members of built-in types (int, pointers, etc.) are not automatically zero-initialized unless explicitly done so in the constructor — a real, common gotcha, especially coming from a language with different defaults. Rust simply doesn't allow reading an uninitialized variable at all — a compile error, closing this exact class of bug structurally.

ConceptC (c3-4)C++-specific additionRust
Core UB categoriessigned overflow, OOB, dangling pointers, etc.inherited unchangedstructurally prevented in safe code
Slicing a polymorphic objectn/a — no inheritancea real logic bug, virtual dispatch silently lostn/a — no equivalent value-slicing concept
Uninitialized member readUB — garbage valueUB — garbage value, same as Ccompile error — cannot read before initializing
Never pass or return a polymorphic object by value
cpp2-6's own advice, now fully justified: catch exceptions by reference, and generally pass/return any polymorphic object by pointer or reference, never by value — value semantics and virtual dispatch simply don't mix safely.
Slicing compiles silently — nothing announces it happened
Object slicing isn't flagged by the compiler by default in most cases. It silently compiles, silently loses data and behavior, and produces no warning at the point it happens — a genuinely dangerous class of bug specifically because nothing announces it.

Coding Challenges

Challenge 1

Reproduce this chapter's own Shape/Circle slicing example: assign a Circle to a plain Shape variable by value, call area() on the sliced Shape, and confirm it calls Shape's own implementation rather than Circle's.

📄 View solution
Challenge 2

Fix Challenge 1 so that calling area() correctly dispatches to Circle's own implementation, without changing anything about how the Circle object itself is constructed.

📄 View solution
Challenge 3

Explain precisely why calling a virtual function from a base class's own constructor doesn't dispatch to a derived override, tying your answer to exactly when the object's vtable pointer is updated during construction.

📄 View solution

Chapter 5 Quick Reference

  • Every UB category from c3-4 applies unchanged in C++ — nothing re-derived here
  • Object slicing — assigning a derived object to a base BY VALUE loses the derived part and virtual dispatch, silently
  • Calling a virtual (especially pure virtual) function from a base constructor doesn't see the derived override — the vtable pointer isn't finalized yet
  • C++ built-in-type members are not zero-initialized by default — unlike Rust, which refuses to compile a read before initialization
  • Never pass/return polymorphic objects by value — always by pointer or reference
  • Next chapter: the capstone — a modern C++ project combining everything from this entire track