Search code examples
c++gcclanguage-lawyercompiler-optimizationundefined-behavior

Is there any compiler flag that I could use in the code below to report a warning about UB?


I'm not very familiar with the GCC compiler, and I'm trying to run a code using two source files in wandbox. Let's start with the first code using the main file (default) and a second source file named other.cc, as follows:

main file

#include <iostream>
struct A {
    int i = 1;
    static const int k = 1;
};
extern A a;    // The object a is defined in other.cc

int main() {
    std::cout << a.i << '\n';
    std::cout << a.k << '\n';
}

other.cc

struct A {
    int i = 1;
    static const int k = 1;
};
A a{2};

Note that I have to insert the name of the file other.cc in the box Compiler Options, on the left of the Wandbox, in order for this file to be compiled and linked into the final object file. Running this code I get the numbers 2 and 1 printed below, and they are correct.

2
1

Now, if I exclude the file other.cc from the compilation and linking processes, by deleting its name from the Compilation options box, I get a linking error, about the reference to the non-static data member A::i being used in the expression

std::cout << a.i << '\n';

If I then erase this statement from the code, it apparently runs normally, because the compiler replaces the variable a.k by its constant value 1 in the remaining statement std::cout << a.k << '\n'; in main(), printing 1. But this is considered undefined behavior, according to [basic.def.odr]/10 and [intro.compliance]/2 (2.3). Note the "no diagnostic required" mentioned on [basic.def.odr]/10.

I'm trying then to force the compiler to emit an error in this case, by the use of some flag that will prevent this optimization. I have already tried with the flags -fkeep-static-consts and -fno-keep-static-consts, to no avail. Is there any other flag that I could use to avoid this undefined behavior?

I got this example from this discussion in C++ std-discussion.

I know that by defining the object a in the first file would solve the problem. But that's not what I am looking for with this weird example. I'm just trying to get a better picture of how compilers work in these unusual circumstances.


Solution

  • I think the answer to a different question would be more illuminating. The dread of undefined behavior lies in the inability to guarantee the same runtime results when using different compilers. So:

    Don't I need an error message to ensure that runtime behavior does not depend on the compiler used? No. There are two cases to consider.

    First case: the compiler replaces a.k with the value 1. The runtime behavior is the intended result.

    Second case: the compiler does not replace a.k with a fixed value, instead producing some machine code that references the a variable. (This is what you are trying to trigger with a compiler option.) In this case, the linker sees the reference and will emit an error when the variable is not found in any of the translation units. You cannot progress to runtime behavior, so you do not get runtime results that are inconsistent with the first case.

    So there could in theory be a few pains when switching compilers, but not the obscure bugs typical of undefined behavior.

    In case some do not find this answer illuminating, I now return to the original question, rephrased a bit.

    Is there any flag I can use to force a diagnostic message (error or warning) when this sort of violation of the one-definition rule occurs? Depends on the compiler, but probably not. Note that [basic.def.odr]/10 mentions "no diagnostic required", so a compiler is not required to provide a diagnostic message when this particular violation occurs. Why not? Presumably because of the above: either a message will be triggered at a later stage (i.e. linking) or the intended behavior will shine through. There is no benefit to increasing the compiler's bookkeeping burden by requiring an error/warning in this case.