Aussie AI Blog

100 C++ Memory Safety Techniques

  • 7th March, 2025
  • by David Spuler, Ph.D.

100 C++ Memory Safety Techniques

As a response to Bjarne Stroustrup's call to action regarding "C++ attacks" related to memory safety [1], I decided to compile a list of the possible methods, and count them. It's disheartening to see ongoing knee-jerk reactions to C++ memory issues, being effectively just to ban it. The plan to replace C++ with another programming language, usually Rust, is not a good idea because:

  • It's expensive,
  • It's not necessary, and
  • It's too slow.

It's much more expensive to hire new programmers and do a ground-up rewrite of your application, than it is to refactor the code with extra memory safety mitigations. Admittedly, your C++ programmers are already expecting to get fired because of AI, so go crazy if you really like firing people. I guess you could be kinder and ask your C++ programmers to retrain in Rust, but then what you really have is a bunch of newbie programmers writing your business applications.

And it's not necessary. The cost of upgrading C++ to better memory safety is much less. Even upcoming standards improvements, such as "Profiles" as espoused by Bjarne Stroustrup [2], will only require code changes similar to getting C++ to work with a very picky compiler (because Profiles are statically enforced by the compiler). Similarly, adding pragmatic memory safety approaches is similar to a code refactoring effort, not a rewrite. Furthermore, some approaches that you can do today involve the use of new builds, new compiler tools, and upgraded standard libraries, rather than code changes.

Which one is faster? The ground-up rewrite doesn't sound that fast to me. Many of the pragmatic code changes are a refactoring effort, where many existing techniques can be integrated into existing code bases in a day or so. Similarly, changes to the tools in build, compilers, static analyzers, and runtime checkers, are all very fast, with no code changes (except when they find bugs, haha!).

Here's the List So Far

Herewith, I provide a list of all the C++ memory safety risk mitigation methods of which I'm aware, in the categories of:

  • Upcoming C++ language safety improvements (e.g., Profiles [2], Safe C++).
  • Already-completed and ongoing C++ mitigation work (e.g., C++ Core Guidelines, hardening standard C++ libraries).
  • Pragmatic memory safety coding approaches (e.g., safety wrapper functions).

Without further ado...


    C++ Safety Future Standardization Efforts:
  1. Profiles [2] — supported by Bjarne Stroustrup, with C++ memory-safety enforceable by the compiler statically.
  2. Safe C++ — C++ language extensions with "safe" and "unsafe" keywords.
  3. TrapC
  4. FilC
  5. Mini-C

    Existing C++ Safety Guidelines:
  6. C++ Core Guidelines — from C++ standardization gurus Bjarne Stroustrup and Herb Sutter.
  7. SEI CERT C++ Secure Coding Guidelines
  8. SEI CERT C Secure Coding Guidelines

    Big Quality Improvements (General Approaches):
  9. Automate full test runs regularly — use CI/CD, or nightly builds when it gets too slow for CI/CD.
  10. Nightly build tests with sanitizers/runtime checkers — detect bugs as early as possible.
  11. Fuzzing — thrash your code with lots of weird stuff, long inputs, etc.
  12. Fuzzing with runtime sanitizers — it's slow, but worth it.
  13. AI code checking and debugging — it's already good, and will be great soon.
  14. Review "technical debt" — but fixing it is not usually as impactful as programmers think.

    Build Improvements for Safety:
  15. Run memory safety checkers and sanitizers regularly — e.g., nightly builds.
  16. Use multiple runtime memory safety checkers — Valgrind, ASan, MSVS, etc.
  17. Build a custom memory-safe coding style enforcer — e.g., even grep for a file of patterns containing the names of memory-unsafe functions works quite well.
  18. Extra warnings — separate build path for running compilers enabled with extra picky warnings.
  19. Optimizer levels — separate build path for running code with different optimizer levels to shake out rare but insidious memory errors that only occur when optimized.
  20. Run builds on multiple platforms of your core platform-independent code (Windows, Linux, Mac) — more compiler warnings, more ways to thrash the code at runtime, more sanitizers (can spin up a cloud virtual machine for whatever platforms you need).

    General Safe Coding Style Improvements:
  21. Assertions
  22. Check return codes
  23. Validate incoming parameter values in functions
  24. Debug tracing macros (logging)
  25. Unit tests (can never have too many)
  26. Module-level tests
  27. Automated integration tests
  28. Exception handling — consider whether to use C-style return codes versus C++ try/catch exceptions.
  29. Painstaking work — adding reliability to code is endless small improvements, not just throw an exception and you're done.

    Specific Coding Improvements for C++ Memory Safety:
  30. Use macro wrappers to ensure checking of return codes for common functions (both library and custom code) — this is more general than [nodiscard] which guarantees only that the return code is assigned somewhere, but not that it's well handled, whereas a wrapper guarantees that failures are at least logged somewhere (and then hopefully properly handled by the caller).
  31. Unreachable code marked with assertions or other handling.
  32. Not-yet-implemented code marked with assertions or other handling.
  33. Safety wrapper functions for common library or non-library functions — validate inputs, check for common usage errors, and check the return value for failure so it's never undetected.
  34. Detect uncommon "undefined behavior" in wrapper functions — e.g., overlapping memory blocks in memcpy, file read and write without intervening seek, etc.
  35. Intercept fatal signals (e.g. SIGSEGV) with a handler that at least prints a nice message and ideally even a stacktrace — no, you can't really recover at this point, but you can provide extra debugging context for supportability; also watch out it doesn't get re-raised.

    DIY Memory Safety Classes:
  36. Safe smart buffer class (two-variable method) — add a second "buffer safety checker" object to watch an existing buffer.
  37. Safe smart buffer class (one-variable method) — replace simple buffer variables with a templated smart buffer object of the required size

    Heap Memory Safety Methods for C++:
  38. Macro interception of C-style memory primitives (malloc, calloc, free, strdup, etc.)
  39. Link-time interception of C++ new/delete memory primitives — it's been a standard feature of C++ for many years.
  40. Implement a randomized delayed deallocator — this blocks most Use-After-Free attack vectors.

    DIY Heap Memory Safety Wrapper Libraries for C++:
  41. Validate all memory primitives (e.g., detect free non-heap, double-deallocation, mixed C/C++ memory primitives, null pointers, overlapping memory blocks, and more.)
  42. Use canary values for a fast way to detect buffer overflows.
  43. Use "last-byte-null" canary values in string buffers.
  44. Use redzones to detect buffer overflows.
  45. Detect buffer underflows with canaries and redzones.
  46. Poison uninitialized or freed memory blocks
  47. Poison after the null byte in long buffers containing short strings.
  48. Implement a "never-free" approach to detect all Use-After-Free errors — probably only in testing, not production.
  49. Check memory block size parameters are not equal to sizeof(char*) — indicating sizeof used on a pointer or array parameter, then passed to memset or other functions.
  50. Find memory block sizes in safety wrapper functions — each platform has its own way to find the size of an allocated block using the address of the start of the block (but not from the middle of the block).

    Stack Memory Safety Methods for C++:
  51. Macro interception of C-style stack memory primitives (alloca, other variants, etc.)
  52. Use a runtime sanitizer that detects stack buffer overflows (i.e., not Valgrind).
  53. Use a stack overflow canary object as a local variable — the constructor sets it to a fixed value, and the destructor checks the value hasn't changed, or calls abort if it has. Use volatile to prevent a smart optimizer removing it.
  54. Use a semi-randomized stack overflow canary object — to further thwart stack buffer overflow attacks, don't just use a fixed value, but a semi-random value that changes with time.
  55. Use an obscured pair of numbers in a semi-randomized stack object — both values are stored in the stack object and can be overrwitten, so even further you can either: (a) allocate heap memory for the expected value (effective but inefficient), or (b) use some obscure formula to generate and validate the two values.

    Compile-Time Memory Safety Methods for C++:
  56. Warning-free compilation policy — fix all compiler warnings, even the "unused variable" ones, lest they hide serious bugs.
  57. Use static_assert — e.g., check the sizeof for your types at compile-time (avoids portability glitches later).

    Specific Coding Improvements for Non-Memory Safety:
  58. Add loop counters to detect and prevent infinite loops
  59. Add [nodiscard] to your functions to detect thrown-away return codes.
  60. Add a standardized stack trace reporting library — use std::backtrace, Boost or Gnu; useful for assertions, return code failures, memory failures, etc.
  61. Add math function wrappers — e.g., cos(90) is probably mistaking radians and degrees.
  62. Add fopen/fclose file function wrappers — prevent double fclose errors, and other crashes.
  63. Safe integer classes to detect overflow — a little slow for my taste.

    Weird arithmetic problems:
  64. Check for arithmetic overflow and underflow
  65. Check for floating-point overflow and underflow
  66. Beware floating-point denormalized values — tiny and weird.
  67. Two floating-point zeros — there is now a level negative-zero.
  68. Floating point has many different values for infinity and negative infinity.
  69. Order of evaluation errors on various binary operators.
  70. Order of evaluation errors on function call arguments.
  71. Floating-point runtime error checker tools — seems like these are only in research papers, not used commercially yet.

    Basic Standard C++ Coding Changes:
  72. Use references not pointers — references are a longstanding compiler-enforced way to replace pointers, with zero extra runtime cost.
  73. Use "const correctness" — I'm not a fan of this, because it's a lot of busy work if the code doesn't already adhere to proper const usage, and fixing it rarely finds any real bugs.

    Unsafe C-style Functions:
  74. Change sprintf to snprintf — but beware using its return value.
  75. Use safe versions of unsafe string primitives (e.g., strcpy, strcat)
  76. Avoid or wrap strncpy — it's actually unsafe despite having a buffer size parameter because it can leave a string without a null byte.
  77. Avoid fflush(stdin) — officially undefined behavior, although I've seen it used and it's simply a nop.
  78. Avoid the old strncat function — Write your own safe version of strcat instead.
  79. Prefer memmove to memcpy — it handles overlapping ranges without failure.
  80. Avoid longjmp and setjmp for exception handling — they're old, unsafe, and superceded by many other options.
  81. Avoid tmpfile and mktemp — they have a race condition that's a security risk.
  82. Avoid scanf and sscanf — they're just a hot mess!

    Standard C++ Library Memory Safety Techniques:
  83. Use std::string not char* or char[].
  84. Use std::span — safe view onto other array data.
  85. Use std::mdspan — safe view onto multi-dimensional data.
  86. Use standard data structure classes — don't write your own hash table ever again.
  87. Use standard C++ smart pointer types (e.g. shared_ptr, weak_ptr, etc.)

    Sanitizer Tools for Runtime C++ Memory Safety Checking:
  88. Valgrind (free on Linux)
  89. ASan (address sanitizer, free)
  90. Various commercial runtime memory checkers

    Compilers for C++ Memory Safety:
  91. GCC warning options (-Wall is my favorite, and there's -Wextra, -Wpointer-arith, and various others)
  92. CLANG warnings — enable more to check more.
  93. MSVS warnings — click on some checkboxes.

    Linters for C++ Memory Safety:
  94. Use compilers and extra warnings as linters
  95. Set up a separate "lint" build path (e.g., "make lint")
  96. Use freeware linters
  97. Use commercial linters

    Library Improvements for C++ Memory Safety:
  98. C++ standard library hardening efforts
  99. libc++ hardening
  100. Debug versions of libc++ — with extra error checking, self-validation, and instrumentation.

Sigh. Okay, so I admit they're not all about memory safety, but some are more about software quality and non-memory undefined behaviors. On the other hand, I didn't list out every different warning to use for each compiler, every free and commercial lint checker, every different type of memory error, and so on.

References

  1. Thomas Claburn, Sun 2 Mar 2025, C++ creator calls for help to defend programming language from 'serious attacks': Bjarne Stroustrup wants standards body to respond to memory-safety push as Rust monsters lurk at the door, The Register, https://www.theregister.com/2025/03/02/c_creator_calls_for_action
  2. Bjarne Stroustrup, March 2025 (accessed), ``Profiles'' -- What we need, https://github.com/BjarneStroustrup/profiles

Blog Articles on C++ Memory Safety

Some of our other writings on this topic:

Safe C++ Book



Safe C++: Fixing Memory Safety Issues The new Safe C++ coding book by David Spuler:
  • Memory Safety
  • Rust versus C++
  • The Safe C++ Standard
  • Pragmatic Memory Safety

Get your copy from Amazon: Safe C++: Fixing Memory Safety Issues

More AI Research Topics

Read more about: