Home > other >  What is a good technique for compile-time detection of mismatched preprocessor-definitions between l
What is a good technique for compile-time detection of mismatched preprocessor-definitions between l

Time:04-05

Motivating background info: I maintain a C library, and I spent way too much time this weekend tracking down a mysterious memory-corruption problem in an application that links to this library. The problem eventually turned out to be caused by the fact that the C library was built with a particular -DBLAH_BLAH compiler-flag, while the application's code was being compiled without that -DBLAH_BLAH flag, and that led to the library-code and the application-code interpreting the classes declared in the library's header-files differently in terms of data-layout. That is: sizeof(ThisOneParticularClass) would return a different value when invoked from a .cpp file in the application than it would when invoked from a .cpp file in the library.

So far, so unfortunate -- I have addressed the immediate problem by making sure that the library and application are both built using the same preprocessor-flags, and I also modified the library so that the presence or absence of the -DBLAH_BLAH flag won't affect the sizeof() its exported classes... but I feel like that wasn't really enough to address the more general problem of a library being compiled with different preprocessor-flags than the application that uses that library. Ideally I'd like to find a mechanism that would catch that sort of problem at compile-time, rather than allowing it to silently invoke undefined behavior at runtime. Is there a good technique for doing that? (All I can think of is to auto-generate a header file with #ifdef/#ifndef tests for the application code to #include, that would deliberately #error out if the necessary #defines aren't set, or perhaps would automatically-set the appropriate #defines right there... but that feels a lot like reinventing automake and similar, which seems like potentially opening a big can of worms)

CodePudding user response:

One way of implementing such a check is to provide definition/declaration pairs for global variables that change, according to whether or not particular macros/tokens are defined. Doing so will cause a linker error if a declaration in a header, when included by a client source, does not match that used when building the library.

As a brief illustration, consider the following section, to be added to the "MyLibrary.h" header file (included both when building the library and when using it):

#ifdef FOOFLAG
extern int fooflag;
static inline int foocheck = fooflag;    // Forces a reference to the above external
#else
extern int nofooflag;
static inline int foocheck = nofooflag;  //                <ditto>
#endif

Then, in your library, add the following code, either in a separate ".cpp" module, or in an existing one:

#include "MyLibrary.h"

#ifdef FOOFLAG
int fooflag = 42;
#else
int nofooflag = 42;
#endif

This will (or should) ensure that all component source files for the executable are compiled using the same "state" for the FOOFLAG token. I haven't actually tested this when linking to an object library, but it works when building an EXE file from two separate sources: it will only build if both or neither have the -DFOOFLAG option; if one has but the other doesn't, then the linker fails with (in Visual Studio/MSVC):

error LNK2001: unresolved external symbol "int fooflag" (?fooflag@@3HA)

The main problem with this is that the error message isn't especially helpful (to a third-party user of your library); that can be ameliorated (perhaps) by appropriate use of names for those check variables.1

An advantage is that the system is easily extensible: as many such check variables as required can be added (one for each critical macro token), and the same idea can also be used to check for actual values of said macros, with code like the following:

#if FOOFLAG == 1
int fooflag1 = 42;
#elif FOOFLAG == 2
int fooflag2 = 42;
#elif FOOFLAG == 3
int fooflag3 = 42;
#else
int fooflagX = 42;
#endif

1 For example, something along these lines (with suitable modifications in the header file):

#ifdef FOOFLAG
int CANT_DEFINE_FOOFLAG = 42;
#else
int MUST_DEFINE_FOOFLAG = 42;
#endif

Important Note: I have just tried this technique using the clang-cl compiler (in Visual Studio 2019) and the linker failed to catch a mismatch, because it is completely optimizing away all references to the foocheck variable (and, thus, to the dependent fooflag). However, there is a fairly trivial workaround, using clang's __attribute__((used)) directive (which also works for the GCC C compiler). Here is the header section for the last code snippet shown, with that workaround added:

#if defined(__clang__) || defined(__GNUC__)
#define KEEPIT __attribute__((used))
// Equivalent directives may be available for other compilers ...
#else
#define KEEPIT
#endif

#ifdef FOOFLAG
extern int CANT_DEFINE_FOOFLAG;
KEEPIT static inline int foocheck = CANT_DEFINE_FOOFLAG; // Forces reference to above
#else
extern int MUST_DEFINE_FOOFLAG;
KEEPIT static inline int foocheck = MUST_DEFINE_FOOFLAG; //         <ditto>
#endif

CodePudding user response:

As an alternative to @adrian's (excellent) answer, here's a suggestion for a runtime check which might be of interest.

For the sake of example, let's assume there are two flags, FOO1 and FOO2. First of all, for my scheme to work, and since the OP seems to be using #ifdef rather than #if, the library needs to provide a header file that looks like this (header guards omitted for clarity):

// MyLibrary_config_check.h

#ifdef FOO1
    #define FOO1_VAL 1
#else
    #define FOO1_VAL 0
#endif

#ifdef FOO2
    #define FOO2_VAL 1
#else
    #define FOO2_VAL 0
#endif

... etc ...

Then, the same header file declares the following function:

bool CheckMyLibraryConfig (int expected_flag1, int expected_flag2 /* , ... */);

The library then implements this like so:

bool CheckMyLibraryConfig (int expected_flag1, int expected_flag2 /* , ... */)
{
    static const int configured_flag1 = FOO1_VAL;
    static const int configured_flag2 = FOO2_VAL;
    // ...

    if (expected_flag1 != configured_flag1)
        return false;
    if (expected_flag2 != configured_flag2)
        return false;
    // ...
    return true;
}

And the consumer of the library can then do:

    if (!CheckMyLibraryConfig (FOO1_VAL, FOO2_VAL /* , ... */))
        halt_and_catch_fire ();

On the downside, it's a runtime check, and that's not what was asked for. On the upside, CheckMyLibraryConfig could instead be implemented something like this:

std::string CheckMyLibraryConfig (int expected_flag1, int expected_flag2 /* , ... */)
{
    if (expected_flag1 != configured_flag1)
        return std::string ("Expected value of FOO1 does not match configured value, expected: ")   std::to_string (expected_flag1)   ", configured: "   std::to_string (expected_flag2);

    ...

    return "";
}

And the consumer can then check for and display any non-empty string returned. Get as fancy as you like (that code could certainly be factored better) and check all the flags before returning a string reporting all the mis-matches, go crazy.

  • Related