





















































Hi ,
Welcome to a brand new issue of ProgrammingPro.
In today’sExpert Insight, we bring you an excerpt from the recently published book, C++ Memory Management, which discuses three major categories of dangerous behavior in C++—ill-formed code with no required diagnostics, undefined behavior that compilers may optimize around unpredictably, and implementation-defined behavior that risks portability.
News Highlights: Sonatype flags 18,000 open source malware packages in Q1, most targeting data theft; GitHub rolls out security campaigns with Copilot autofix to reduce security debt; Rust 1.86 adds trait upcasting; and Kotlin, Swift, and Ruby drop from Tiobe’s top 20.
My top 5 picks from today’s learning resources:
But there’s more, so dive right in.
Stay Awesome!
Divya Anne Selvaraj
Editor-in-Chief
subprocess.run()
in Python to execute system commands safely and efficiently.eval
, loop scoping, falsy values like document.all
, inconsistent string iteration due to Unicode, sparse arrays that break array methods, and more.->>
).unwrap
, and misuse of serialization or default values and more.Dom\HTMLDocument
class to produce neatly indented, human-readable HTML output.db_postgres
module can be vulnerable to SQL injection when standard_conforming_strings
is disabled, due to flawed manual escaping in its parameterization logic.OptionParser
makes building flexible, user-friendly CLI tools easy—without relying on external gems—by showcasing advanced usage.Here’s an excerpt from “Chapter 2: Things to Be Careful With" in the book, C++ Memory Management, by Patrice Roy, published in March 2025.
Before delving into some actual practices that require care, it’s interesting to look at the main categories of risks we could run into if our code does not respect the rules of the language. With each such category comes a form of unpleasantness we should striveto avoid.
Some constructs in C++ are said to beIll-Formed, No Diagnostic Required(IFNDR). Indeed, you will find quite a few occurrences in the standard of“if […], the program is ill-formed, with no diagnostic required.”When something is IFNDR, it means your program is broken. Bad things could happen, but the compiler is not required to tell you about them (indeed, sometimes, the compiler does not have sufficient information to diagnose theproblematic situation).
One Definition Rule(ODR) violations, to which we will return in theThe ODRsection later in this chapter, fall under IFNDR. However, there are other such cases, such as having a global object that has different alignment requirements (throughalignas
) in different translation units (different source files, essentially), or having a constructor that delegates to itself either directly or indirectly. Here isan example:
class X {
public:
// #0 delegates to #1 which delegates to #0 which...
X(float x) : X{ static_cast<int>(x) } { // #0
}
X(int n) : X{ n + 0.5f } { // #1
}
};
int main() {}
Note that your compiler might give a diagnostic; it’s just not required to do so. It’s not that compilers are lazy – they might even be unable to provide a diagnostic in some cases! So, be careful not to write code that leads toIFNDR situations.
We mentionedUndefined Behavior(UB) inChapter 1. UB is often seen as a source of headaches and pain for C++ programmers but it refers to any behavior for which the C++ standard imposes no requirement. In practice, this means that if you write code that contains UB, you have no idea what’s going to happen at runtime (at least if you’re aiming for somewhat portable code). Canonical examples of UB include dereferencing a null pointer or an uninitialized pointer: do that and you’ll be inserious trouble.
To compilers, UB is not supposed to happen (code that respects the rules of the language does not contain UB, after all). For that reason, compilers “optimize around” code that contains UB, to sometimes surprising effect: they might begin removing tests and branches, optimizing loops away, andso on.
The effects of UB tend to be local. For instance, in the following example, there is a test that ensures thatp
is not null before using*p
in one case, but there is at least one access to*p
that is unchecked. This code is broken (the unchecked access to*p
is UB), so the compiler is allowed to rewrite it in such a way that all tests to verify thatp
is not null are effectively removed. After all, the damage would be done ifp
werenullptr
, so the compiler is entitled to assume that the programmer passed a non-null pointer tothe function!
int g(int);
int f(int *p) {
if(p != nullptr)
return g(*p); // Ok, we know p is not null
return *p; // oops, if p == nullptr this is UB
}
The whole body off()
could legitimately be rewritten by your compiler asreturn g(*p)
in this case, with thereturn *p
statement being turned intounreachable code.
The potential for UB hides in various places in the language, including signed integer overflow, accessing an array out of bounds, data races, and so on. There are ongoing efforts to reduce the number of potential UB cases (there’s even a study group,SG12, dedicated to this effort), but UB will likely remain part of the language for the foreseeable future, and we need to be awareof it.
Some parts of the standard fall under the umbrella ofimplementation-defined behavior, or behavior that you can count on with a specific platform. This is behavior that your platform of choice is supposed to document, but that is not guaranteed to be portable toother platforms.
Implementation-defined behavior occurs in many situations and includes such things as implementation-defined limits: the maximum number of nested parentheses; the maximum number of case labels in a switch statement; the actual size of an object; the maximum number of recursive calls in aconstexpr
function; the number of bits in a byte; and so on. Other well-known cases of implementation-defined behavior include the number of bytes in anint
object or whether thechar
type is a signed or an unsignedintegral type.
Implementation-defined behavior is not really a source of evil, but it can be problematic if one strives for portable code but depends on some non-portable assumptions. It is sometimes useful to spell one’s assumptions in code throughstatic_assert
when the assumption can be validated at compile-time or some similar, potentially runtime mechanisms in order to realize—before it’s too late—that these assumptions are broken for a given target platform.
For example:
int main() {
// our code supposes int is four bytes wide, a non-
// portable assumption
static_assert(sizeof(int)==4);
// only compiles if condition is true...
}
Unless you are convinced that your code will never need to be ported to another platform, strive to rely as little as possible on implementation-defined behavior, and if you do, make sure that you validate (throughstatic_assert
if possible, at runtime if there’s no other choice) and document this situation. It might help you avoid some nasty surprises in the future...
C++ Memory Managementwas published in March 2025. Packt library subscribers can continue reading the entire book for free or you can buy the book here!
That’s all for today.
We have an entire range of newsletters with focused content for tech pros. Subscribe to the ones you find the most usefulhere.
If your company is interested in reaching an audience of developers, software engineers, and tech decision makers, you may want toadvertise with us.
If you have any suggestions or feedback, or would like us to find you a learning resource on a particular subject, just respond to this email!