Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions CppCoreGuidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ Philosophy rules summary:
* [P.11: Encapsulate messy constructs, rather than spreading through the code](#Rp-library)
* [P.12: Use supporting tools as appropriate](#Rp-tools)
* [P.13: Use support libraries as appropriate](#Rp-lib)
* [P.14: Do not invoke undefined behavior](#Rp-ub)

Philosophical rules are generally not mechanically checkable.
However, individual rules reflecting these philosophical themes are.
Expand Down Expand Up @@ -1210,6 +1211,42 @@ By default use
If no well-designed, well-documented, and well-supported library exists for an important domain,
maybe you should design and implement it, and then use it.

### <a name="Rp-ub"></a>P.14: Do not invoke undefined behavior

##### Reason

Invoking [undefined behavior](https://en.cppreference.com/w/cpp/language/ub) allows the
compiler to do literally anything. In fact, when undefined behavior is invoked, the compiler
can break a completely different part of the code, even one that didn't invoke undefined behavior
itself! This is a major problem for us for three reasons: correctness, portability, and future-proofing.

Since invoking undefined behavior breaks our side of the compiler/code contract, there
is no way to fully ensure correctness in code that invokes undefined behavior. Unit tests
are not a full solution here since undefined behavior could happen differently in a unit-test
context compared to a real-world context. Additionally, undefined behavior is often
non-deterministic at runtime, which could give us flaky tests or consistent false negatives.
Even worse, undefined behavior can be compiled inconsistently, even on the same compiler
and platform, making root cause identification of failing tests impossible.

We want our code to be portable, and historically we've added new platforms consistently
over time. Undefined behavior can be a portability problem since the undefined behavior
will likely occur in a different way with a new platform or compiler. Unit tests again
aren't a full answer here, since there's no guarantee that the unit tests will behave
the same way as production code when undefined behavior is at play.

We want to keep relatively up-to-date with our tooling. Having undefined behavior
in our codebase can become a barrier to quickly upgrading our compilers or enabling new
optimization passes, since when undefined behavior is at play, these changes have a strong
likelihood of breaking our code in subtle ways.

##### Enforcement

We should run as much code as possible through llvm's "asan" and "ubsan" sanitizer modes, in
CI. Together, these check for many instances of undefined behavior.

Static analyzer tools can also detect some undefined behavior, and we should consider using them
in our CI. Visual Studio's Analyze, Xcode's analyze, and clang-tidy all include some support
for this.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: How to you manage things like POSIX standard functions that have undefined behavior but must be used in the code for one reason or another?

Granted this might effect C code more than C++; but most code bases are probably more mixed between the two.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good question I hadn't thought about this. Can you provide some examples for me to think about? The only one that comes to mind off the top of my head is casting the void* from dlsym to a function pointer.

Copy link

@BenjamenMeyer BenjamenMeyer Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rothmichaels ones that easily come to mind are the POSIX printf family of functions since I've managed them across platforms before. For example, snprintf has some undefined behavior around the buffer provided, the output buffer, and the return values for being able to dynamically determine the size of the buffer needed to hold the output contents. Windows vs Linux vs Mac vs any Unix may operate differently on these methods. Granted, C++ advises using streams instead; but you there may still be requirements for projects to interface with these or similar and they're required to be available for POSIX compliant systems.

Some references, though they more reference C99 and C89 than POSIX; but the methods are POSIX required:
https://linux.die.net/man/3/snprintf
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/snprintf-snprintf-snprintf-l-snwprintf-snwprintf-l?view=msvc-170

For Microsoft, see snprintf vs _snprintf - both are POSIX compliant; one is C99 compliant which does help, but it comes down to what does your platform support since POSIX defines some things as UNDEFINED as a result of the history of UNIX and building a standard.

Copy link
Contributor

@jwakely jwakely Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh? I don't think the suggested guideline means "don't use functions which can ever have UB under some set of conditions". So it's not saying "never use snprintf".

It's saying don't use snprintf in such a way that invokes undefined behaviour.

So using snprintf is ok, using it incorrectly is not.


# <a name="S-interfaces"></a>I: Interfaces

Expand Down