The hardened standard library turns an out-of-bounds read into a stop
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_FASTfor the production-grade level, withEXTENSIVEandDEBUGlevels 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_VECTORand_MSVC_STL_HARDENING_SPAN._ITERATOR_DEBUG_LEVELis 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.