Using #pragma detect_mismatch to help catch ODR violations


One of the more insidious problems you may encounter are those traced back to violations of the C++ One Definition Rule. As a general rule, if the conflicting definitions occur in separate translation units, the behavior is undefined, but no diagnostic is required. The lack of a diagnostic means that if two translation units define a type differently (say, because they were compiled with different compile-time configurations), you may not notice a problem until you start dealing with mysterious memory corruption.

These types of bugs are not fun to diagnose.

If you use the Microsoft Visual C++ toolchain, then you can use the #pragma detect_mismatch("name", "value") directive to give the linker some help in identifying mismatched definitions. Specifically, the linker verifies that all such declarations with the same name also have the same value.

The idea here is that if you have something that is declared differently based on compilation settings, you can emit a different #pragma detect_mismatch("name", "value") for each version, using the same name but a different value. The linker will then verify that everybody used the same version of the header file.

Here's an example:

// This is a fake mutex that does no locking.
struct fake_mutex
{
 void lock() {}
 void unlock() {}
};

class Contoso
{
#ifdef SINGLE_THREADED
   // single-threaded doesn't need a mutex.
   typedef fake_mutex mutex_t;
#else
   // multi-threaded needs a true mutex.
   typedef std::mutex mutex_t;
#endif

public:
  Contoso();

  void Activate()
  {
     std::lock_guard<mutex_t> lock(object_mutex);
#ifndef NDEBUG
    isActivated = true;
#endif
    ... business logic to activate the object ...
  }

  void Charge()
  {
     std::lock_guard<mutex_t> lock(object_mutex);
    // You must activate before you can charge.
    assert(!isActivated);
    ... business logic to charge the object ...
  }

private:
  ...
  mutex_t object_mutex;
#ifndef NDEBUG
  bool isActivated = false;
#endif
};

If this class is used in a project, but one file in the project is compiled with SINGLE_THREADED and another file is compiled without SINGLE_THREADED, or if the two files disagree on NDEBUG, then you have an ODR violation. In practice, this means that bad things will happen if the two files try to access the same Contoso object.

You can use #pragma detect_mismatch to encode which definition is being used. This allows the linker to detect whether a single project uses multiple conflicting definitions.

// This is a fake mutex that does no locking.
struct fake_mutex
{
 void lock() {}
 void unlock() {}
};

class Contoso
{
#ifdef SINGLE_THREADED
   // single-threaded doesn't need a mutex.
   typedef fake_mutex mutex_t;
   #pragma detect_mismatch("Contoso threading", "Single");
#else
   // multi-threaded needs a true mutex.
   typedef std::mutex mutex_t;
   #pragma detect_mismatch("Contoso threading", "Multi");
#endif

#ifdef NDEBUG
   #pragma detect_mismatch("Contoso debug", "Nondebug");
#else
   #pragma detect_mismatch("Contoso debug", "Debug");
#endif

public:
  Contoso();

  void Activate()
  {
     std::lock_guard<mutex_t> lock(object_mutex);
#ifndef NDEBUG
    isActivated = true;
#endif
    ... business logic to activate the object ...
  }

  void Charge()
  {
     std::lock_guard<mutex_t> lock(object_mutex);
    // You must activate before you can charge.
    assert(!isActivated);
    ... business logic to charge the object ...
  }

private:
  ...
  mutex_t object_mutex;
#ifndef NDEBUG
  bool isActivated = false;
#endif
};

You can see the directive in action in this Channel 9 video starring C++ library master Stephan T. Lavavej. The detect_mismatch trick appears around timecode 29:30.

Note of course that you can use this technique for things other than catching ODR violations.

Comments (12)
  1. SimonRev says:

    While I understand that ODR violations are not a required diagnostic by C++ linkers, is there a reason that modern linkers don’t do the requisite check and emit at least a warning? Is performing link-time ODR checking really that computationally expensive?

    1. In the classical model of linking, the linker doesn’t see the definitions. The compiler has already generated object code. All the linker sees is a blob of bytes with fixups.

      1. Matteo Italia says:

        It is true however that the compiler could automatically emit a #pragma detect_mismatch-like for each type/inline function definition, named like the mangled name of the relevant entity and with a hash of the tokens sequence as value (which IIRC is what the standard defines as relevant for the ODR). I don’t think that it would cost so much to implement.

    2. JZ says:

      @SimonRev: If you use GCC 5 or later with -flto-odr-type-merging it will warn about ODR violations by default (disable with -Wno-odr).

  2. How do you recommend checking structure packing options? It would be nice to see an example that shows as many C++ code generation options checked as possible.

  3. pm100 says:

    so the two strings have no meaning? the liner simply looks at all pragma’s called with name ‘foo’ and verifies they are all either ‘wiz’ or ‘bang’? Its not looking at types or externs or anything

    1. exchange development blog team says:

      The example is actually kinda confusing and the MSDN page doesn’t help, does the NDEBUG pragma check tell you that you’ve built one module in debug mode and the other not, if it’s placed in a header file? Do you have to methodically do a pragma_mismatch for every data structure and object in your code to get compete checking?

      1. Drak says:

        I think the idea is that you have 2 or 3 different configurations, and you check with the corresponding number of pragma_mismatch pairs. No need to check each structure if you’re saying ‘in configuration 1 I will use this set of types and in configuration 2 I will use a different set of types’. It’s the configuration that matters, not each specific type. (I think.)

        1. You don’t need to put a check on every data structure, just for every configuration option that affects ODR. If you have twelve “#ifdef NDEBUG”s, you need only one #pragma detect_mismatch(…NDEBUG)” to cover it.

          1. exchange development blog team says:

            Ah, OK, thanks. That makes it quite useful if you only need to do it once per config.

  4. Neil says:

    This reminds me of the problem of wanting to link against two static libraries and find that one is using the static CRT while the other is using the dynamic CRT…

  5. ZLB says:

    A good example of a nasty gotcha!

    Sometimes this con happen through no fault of your own. Eg: Some IDEs will sometimes not rebuild everything if you change a #define in the project file.

    Does Link time code generation pick up on these errors in practice? By deferring code gen until link time, in theory the linker has all the information to know that there are type mismatches.

Comments are closed.

Skip to main content