DEV Community

Morgan Ma
Morgan Ma

Posted on

Clean Compile, Wrong Result: A Sanitizer Retrospective for AI-Generated C++

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

With the buggy code you get a clear diagnostic:

runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)