short

The hardened standard library turns an out-of-bounds read into a stop

· english· audience: working-cpp· AI-generated, reviewed by Filip Sajdak

vector::operator[] with an out-of-bounds index is undefined behavior. In practice that means it reads whatever is next in memory and hands it back as a valid int, and your program keeps running with a wrong answer until, much later and somewhere else, it crashes. at() throws, but nobody writes at() in a hot loop, so the checked path goes unused exactly where it would help.

The hardened standard library changes the default. Build with one macro and operator[] gains a bounds check that, on failure, stops the program immediately instead of reading garbage. For libstdc++ the flag is -D_GLIBCXX_ASSERTIONS; for libc++ it is -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST. The checks cover operator[], span, string_view, iterator ranges, and more.

The demo reads v[1] (in bounds, fine) and then v[5] on a three-element vector. With hardening on, the second access does not return a garbage int; libstdc++ fails its assertion (__n < this->size()) and stops:

v[1] = 20 (in bounds)
hardened stdlib stopped the out-of-bounds access

The stop is a call to std::abort. The demo installs a signal handler purely so it can exit cleanly for Compiler Explorer; in your own process the failed check simply terminates the program where the fault is. A defined crash at the bad index beats an undefined read that corrupts state and surfaces as a mystery a thousand lines later.

The overhead is not the reason to skip it

The instinct is to assume bounds checking is too slow for production. Measured, it usually is not. A predictable branch that the CPU’s branch predictor learns immediately costs close to nothing on the paths that matter, and the compiler eliminates many checks outright when it can prove the index is in range. Google has reported that deploying this class of hardening cut its production segfault rate by roughly a third while leaving performance essentially unchanged, which is why hardening it on by default has become the mainstream advice rather than a paranoid option.

The debug build is checked and the release build is not

GCC turns the libstdc++ assertions on by default at -O0, so the access above is already caught in a debug build without anyone asking for it. The same file at -O2 is a different program:

v[1] = 20 (in bounds)
v[5] = 0 (out of bounds, and we are still running)

It does not crash. It reads whatever sits past the end, prints it, and exits 0. A test suite running that binary records a pass.

Measured on GCC 16.1 with that one file, changing nothing but the flags:

flags exit what happened
-O0 134 assertion fires, naming stl_vector.h:1253
-O2 0 no check, wrong value, clean exit
-O2 -D_GLIBCXX_ASSERTIONS 134 assertion fires
-O2 -fhardened 134 assertion fires

The build you debug in is checked. The build you ship is not, until you say so.

How to turn it on

  • libstdc++ (GCC, Clang with libstdc++): -D_GLIBCXX_ASSERTIONS. The assertions are already on at -O0, so the macro is what carries them into an optimised build. GCC 14 and later also accept -fhardened, an umbrella that adds this alongside _FORTIFY_SOURCE=3, the stack protector and PIE.
  • libc++ (Clang): -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST for the production-grade level, with EXTENSIVE and DEBUG levels above it for testing.
  • MSVC STL: _MSVC_STL_HARDENING=1, shipped in Visual Studio 2022 17.14 and off by default. A failed check calls __fastfail. Individual types can be opted in on their own with macros like _MSVC_STL_HARDENING_VECTOR and _MSVC_STL_HARDENING_SPAN. _ITERATOR_DEBUG_LEVEL is a separate and ABI-breaking setting, not the hardening switch.

Set it in your release build, not just your debug build. The bugs it catches are the ones that reach production.


Sources: libstdc++ hardening / _GLIBCXX_ASSERTIONS · libc++ hardening modes · MSVC STL hardening for _MSVC_STL_HARDENING and the per-type macros. The production-impact figure was reported by Google and cited in the March 2026 WG21 trip report. The flag matrix above was measured on Compiler Explorer with GCC 16.1, the same compiler both demos run on.