C/C++ Weakly-Linked Overridable Values

· danra's blog

#C #C++ #linker #weak

Use the following snippet to define a variable with external linkage and a default value that can be overridden at link-time:

 1#ifdef _MSC_VER
 2#define OVERRIDABLE_VALUE(type, x, ...)    \
 3    extern type x;                         \
 4    extern type default_##x {__VA_ARGS__}; \
 5    __pragma (comment (linker, "/ALTERNATENAME:" MSVC_DECORATE (x) "=" MSVC_DECORATE (default_##x)))
 6#else
 7#define OVERRIDABLE_VALUE(type, x, ...) __attribute__ ((weak)) extern type x {__VA_ARGS__};
 8#endif
 9
10// Example:
11#define MSVC_DECORATE(name) "?" #name "@@3QEBDEB"
12OVERRIDABLE_VALUE (const char* const, git_rev, "00000000")
13#undef MSVC_DECORATE
14// Optional override, possibly in a different translation unit:
15extern const char* const git_rev = "01234567";

This works on Clang, Apple-Clang, GCC and MSVC regardless of:

On GCC and Clang this is done directly by defining a weak symbol, whereas on MSVC the undocumented /ALTERNATENAME linker flag is used.

An extra macro MSVC_DECORATE has to be defined to decorate (mangle) the names of the original and alternate symbols, because /ALTERNATENAME uses decorated names. You can view the decoration MSVC applies by using one of the documented methods or by defining an identity/arbitrary MSVC_DECORATE (or your guess for it if you know MSVC's name decoration scheme by heart) and seeing what unresolved symbol name you get in the linker error (unless you guessed correctly!).

The credit for /ALTERNATENAME goes to the author of this SO answer who revealed the undocumented flag a full 8 years before it was discussed in Microsoft's The Old New Thing developer blog. For a better understanding about the documented, more standard ways to perform link-time overriding using MSVC, and why, unlike the above method, they aren't as resilient to how exactly the link is performed, see this earlier series of posts in the same blog.

My use case for this was removing a dependency of a class implementation on a specific externally-defined variable while keeping existing clients already using that class backwards-compatible, allowing them to migrate later. I set the default value of the variable to some dummy value since it isn't used in the new code path. To be extra foolproof, an assertion could be added during the transition period prior to accessing the weak value to verify that it is not equal to the dummy default value. Once all existing clients are transitioned, the old code path as well as the variable can be removed.