The AI coding boom created a new bottleneck. Not writing code. Reviewing it. Everyone is a reviewer now. Nobody knows what to check.
Here's the dirty secret: most AI-generated code is semantically wrong in subtle ways. The compiler won't tell you. Unit tests won't tell you. Only a sanitizer will.
This post walks through a synthetic but realistic example. You'll see a function that compiles clean, passes naive tests, and still returns garbage. You'll learn a reusable sanitizer loop to catch such bugs. And you can run the whole loop on MonkeyCode's free server option.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The symptom
A function computes the average of two integers. It compiles. It passes the simple test. It returns nonsense for numbers near INT_MAX.
The code:
int average(int a, int b) {
return (a + b) / 2;
}
You've seen this pattern. It appears in tutorials. It appears in old stack overflow answers. It is wrong.
Signed integer overflow is undefined behavior. C++ promises nothing after that. The compiler may assume it never happens. Then it optimizes based on that lie.
Reproducing the failure
Here's a minimal test program.
#include <cstdio>
int average(int a, int b) {
return (a + b) / 2;
}
int main() {
int a = 2147483647;
int b = 2147483647;
int avg = average(a, b);
std::printf("%d\n", avg);
return 0;
}
Compile with -O2. Run it. What do you see?
On most platforms you get -1. The addition wraps around to -2. Then -2 / 2 is -1. The expected answer is 2147483647.
Why did unit tests miss it? They used small numbers. The failure only appears at the boundary. Classic.
The root cause
The root cause is not "bad code". It's the assumption that addition always works. In C++, integer overflow is a minefield.
The safe fix is std::midpoint, added in C++20 for exactly this reason.
#include <numeric>
int average(int a, int b) {
return std::midpoint(a, b);
}
std::midpoint handles overflow and negative numbers correctly. No undefined behavior anywhere.
But here's the real question: would you trust that fix? Would your AI assistant even suggest it? Verification beats trust.
The reusable debugging loop
Sanitizers are the mechanical witness your code review needs. Here's the process I recommend for every AI-generated numeric snippet.
Step 1: Compile with UBSan
UndefinedBehaviorSanitizer catches overflow, null dereferences, and misaligned access.
g++ -fsanitize=undefined -g -O1 main.cpp -o main_ubsan
./main_ubsan
With the buggy code you get a clear diagnostic:
runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
The exact values may vary by compiler. The message is still decisive: undefined behavior happened.
Step 2: Add AddressSanitizer
Memory bugs are a different family. Use -fsanitize=address too.
g++ -fsanitize=address,undefined -g -O1 main.cpp -o main_san
ASAN_OPTIONS=detect_leaks=1 ./main_san
Both sanitizers work in the same binary. They catch different bug classes.
Step 3: Test multiple optimization levels
Undefined behavior often appears only at -O2 or -O3. The optimizer can legally do wild things with UB. So test every level.
for opt in 0 1 2 3; do
g++ -O${opt} -fsanitize=undefined -g main.cpp -o main_${opt}
echo "Optimization level ${opt}:"
./main_${opt} || echo " -> Failed"
done
Sometimes the program passes at -O0 and fails at -O2. That inconsistency is a huge clue.
Why this matters for AI-generated code
You didn't write that code. You don't know its hidden assumptions. The AI might have mixed two idioms. It might have dropped a cast. It might have assumed two's complement without saying so.
Human code review alone is not enough. Subtle UB slips past even experienced eyes. Sanitizers turn "maybe it's broken" into "here is the exact line".
No persuasion. No arguments. Just a runtime error.
Running the loop on MonkeyCode's free server
MonkeyCode is an open-source project with a free tier. It offers free model access and a free server option.
That combination is interesting for this workflow. You paste your AI-generated snippet into a fresh environment. You run the sanitizer loop. You get a verdict. There is no local setup debt and no cost barrier.
The free model can also suggest edge-case tests. But don't trust its suggestions blindly. The sanitizer doesn't care about tone.
Limitations
Sanitizers are not magic. They only detect what they instrument. They miss pure logic errors like "wrong formula, no overflow". They miss performance regressions. They add runtime overhead.
Don't ship sanitizer binaries. Run them in CI or in a disposable sandbox. Keep your production builds clean and fast.
Also, std::midpoint is not a universal fix. Floating-point code has its own dragons. But you will catch the obvious UB.
Should you adopt this?
If you write C++ by hand, yes. If you rely on AI assistants for numeric code, absolutely yes. If your program processes user-controlled input, double yes.
The cost is a few seconds of compile time. The benefit is catching a bug that would have cost you hours later.
Start with one function. Run the sanitizer loop. Fix every warning until clean. Then expand to the whole file.
The parting thought
AI won't replace developers. But AI will keep generating bugs that look like correct code. The only defense is a boring, repeatable verification path.
Compile clean? Fine. Tests pass? Fine. Show me a clean sanitizer report. That's the standard we need.
Try MonkeyCode's free tier for your next C++ experiment. The server is free. The token grant is generous. The bugs it catches can be priceless.
Top comments (0)