Search code examples
c++undefined-behaviorrelease-modedebug-mode

C++ Can Undefined Behavior cause Debug and Release Builds to behave differently?


This question has many great answers explaining why a bug might only manifest itself in a Release build.
Common reasons for bugs in release version not present in debug mode
I have a more specific question regarding undefined behavior.

If a program always appears to work correctly in a Debug build, but behaves differently in a release build (always behaves in the same incorrect way, however), can the problem be due to undefined behavior?


Solution

  • Can be due to undefined behavior? Sure. Is it always because of undefined behavior? Certainly not.

    Imagine this:

    assert(scanf("%d", &n) == 1);
    

    This line will simply get removed in release mode. It is not undefined behavior, but it most certainly will make your program behave in a different way.

    assert may be the obvious example here, but think of this more complicated situation:

    #ifndef NDEBUG
    # define DBG(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
    #else
    # define DBG(fmt, ...) ((void)0)
    #endif
    
    int shared_var;
    
    static inline void do_something_fast(void)
    {
        DBG("doing something fast\n");
        shared_var = something_fast();
    }
    

    and have thread 1:

    ...
    mutex_unlock();
    local_var = shared_var;
    ...
    

    and thread 2:

    ...
    mutex_lock();
    do_something_fast();
    ...
    

    Now this example is totally absurd, but something along these lines are quite common in multi-threaded environments. In this example, this is what happens:

    • thread 1: calls mutex_unlock, waking up thread 2 and going to sleep
    • thread 2: calls do_something_fast()
      • in release: fprintf is called, causing thread 2 to sleep
        • now thread 1 copies the old value of shared_var into local_var
      • in debug: thread one overwrites shared_var
        • now thread 1 copies the new value of shared_var into local_Var

    As you can see, in this example blocking I/O forced a certain behavior between threads, which was itself enabled only in debug mode. This also means that a program working in debug may behave differently than when compiled for release.