5

One of the namespaces in my program is spread between two files. One provides the "engine", the other uses the "engine" to perform various commands. All of the initializations are performed on the "engine" side, including caching parameters fetched from setup library.

So, there's engine.cpp with:

    #include <stdio.h>
    #include "ns.h"

    namespace MyNS
    {

        unsigned char variable = 0;

        void init()
        {
            variable = 5;
            printf("Init: var = %d\n",variable);
        }

        void handler()
        {
            // printf("Handler: var = %d\n",variable);
        }
    }

The variable happens never to be used again in engine.cpp but it's extensively used in commands.cpp.

    #include <stdio.h>
    #include "ns.h"

    namespace MyNS
    {
       extern unsigned char variable;

      void command()
       {
          printf("Command: var = %d\n",variable);
       }
    }

After compiling and linking, I'm getting:

Init: var = 5
Command: var = 1

Now, if I uncomment the printf() in handler() I'm getting:

Engine: var = 5
Command: var = 5
Handler: var = 5

What would be the "correct" way to force GCC not to optimize it away in such a way that accessing it through extern from the other file would fetch the right value? Preferably without reducing the -O level for the rest of the application?

(for completeness case, main.h and ns.h: )

    #include "ns.h"

    int main(int argc, char** argv)
    {
        MyNS::init();
        MyNS::command();
        MyNS::handler();

        return 0;
    }

    namespace MyNS
    {
        void init();
        void command();
        void handler();
    }

This minimized testcase doesn't exhibit this particular behavior; it seems one needs this situation to occur in much more complex environment to happen...

SF.
  • 13,549
  • 14
  • 71
  • 107
  • I don't think the compiler would optimize `variable` away otherwise I don't think the program would link. – Galik Oct 28 '14 at 12:50
  • 3
    I'm having a hard time believing that an assignment to a variable with external linkage would be optimized away. Are you sure it isn't the call to `init` that is being optimized away? – Dark Falcon Oct 28 '14 at 12:50
  • The code would print "Init: ..." rather than "Engine: ...", I suppose you mean these relate to the same case. Anyway, this is a strange behaviour that makes me wonder in what way the other functions affect `variable`. – E_net4 Oct 28 '14 at 12:51
  • What is `command_x()`? Should it be `inline`? – Galik Oct 28 '14 at 12:51
  • 1
    I think maybe this is a static initialization order problem? – Galik Oct 28 '14 at 12:54
  • @Galik: I don't know if it entirely optimizes it away, but the result definitely doesn't propagate to the other file. – SF. Oct 28 '14 at 12:54
  • 2
    Maybe you want `volatile` .... – Basile Starynkevitch Oct 28 '14 at 12:54
  • @DarkFalcon: init() does a lot of other stuff, besides, its printf() executes. – SF. Oct 28 '14 at 12:55
  • 4
    I can't reproduce this with a small example - the compiler should not optimize this away as you claim. Can you create a *complete* compilable example that show this ? Also, what platform and gcc version are you using, any particular compiler flags used ? Anything else out of the ordinary going on, e.g. multi threading, dynamically loaded shared libraries , are your command_x()/init() functions actually called from the constructor of global objects, etc. ? – nos Oct 28 '14 at 12:55
  • @Galik: command_x is one of numerous, various commands that depend on value of the variable. Used here just as an example. – SF. Oct 28 '14 at 12:57
  • @BasileStarynkevitch: I believe it would work, but the variable once set remains `const` to the end of life of the program. I don't want to cripple all other optimizations by making it `volatile` while it's not (both engine and commands work in the same thread, so no problems of cross-thread propagation.) – SF. Oct 28 '14 at 12:59
  • @nos: Crosscompiling from MinGW gcc to ARM, `arm-ts-linux-gnueabi-g++.exe -O3 -Wall -c -fmessage-length=0` (plus lots of Include paths). Linking is with the same `arm-ts-linux-gnueabi-g++.exe -s ` (plus assorted libraries). The only *funny* stuff is that the thread is running under SCHED_FIFO (yielding time to OS only voluntarily), but other than that, init() is called in a long sequence of initializations long after many other parts have initialized, original thread of main(). – SF. Oct 28 '14 at 13:17
  • Then you are either looking at a compiler bug, or you are performing undefined behavior somewhere , e.g. a race condition on the `variable` in question (Such a race condition, which is undefined behavior, could cause the compiler to elide a memory store related to the variable at compile time) - or you've done something else you're not supposed to, such as relying on order of global constructors, designated a member function as `const` when it actually isn't. – nos Oct 28 '14 at 13:21
  • 1
    also, I'll try to write a minimized testcase. In its current form the program is far too big to just "crop some lines". – SF. Oct 28 '14 at 13:22
  • ...well, I did a reduced testcase and the bug doesn't appear. It seems whatever of the bulk of other stuff that goes on along the way breaks it. – SF. Oct 28 '14 at 13:53

1 Answers1

1

eh... the solution was quite trivial.

I exchanged places of the declaration and definition of the variable.

engine.cpp:

extern unsigned char variable;

command.cpp:

unsigned char variable = 0;

That way the compiler has no doubts about need for this variable's existence while compiling commands and in engine it has to reach to the existing instance, it can't just create a temporary one on the spot.


EDIT: Now I've discovered another peculiarity. The value changes depending on where it's written to. The section of code in question is:

1:   varso = SharedObject::Instance()->varso;
2:  memset(det_map,0,sizeof(det_map));
3:  memset(gr_map,0xFF,sizeof(gr_map));
4:  memset(gr_ped,false,sizeof(gr_ped));
5:  memset(&stan,0,sizeof(stan));

6:  stan.SOTUstage = 1;
7:  PR_SOTU = varso->NrPSOTU;

The variable occurs near a place where several arrays are initialized with memset. The variable in question is PR_SOTU (the uppercase is inherited from when it was still a macro, and since it acts along with several other macros acting in a very similar context, it's likely to stay that way).

If move the assignment from its line 7 and place it after lines 1, 2 or 3, it receives the correct value 5. Placed after line 4 it gets the value 18. Anything below, and the value is 1. I moved definition of the variable to a different place (it was the last on the list of all namespace-globals, now it's first) to exclude possibility something writes at that specific memory location, but the behavior remains.

SF.
  • 13,549
  • 14
  • 71
  • 107
  • 2
    The compiler shouldn't be allowed to optimize away usage of the extern variable anyway. I suspect something else is going on. – bames53 Oct 28 '14 at 14:18
  • @bames53: See my edit. This behavior is really peculiar. – SF. Oct 28 '14 at 14:56
  • 4
    It's almost certain that what you're seeing is the result of undefined behavior. Get some analysis tools, static analyzers, valgrind, address and memory sanitizers, etc., and find what the program is doing wrong. – bames53 Oct 28 '14 at 15:44
  • 2
    This sounds like the static initialization order fiasco. Set a breakpoint at the point where you get the wrong behavior, and I bet you'll find that you're in a place that's not guaranteed to happen before the initialization that you are expecting to happen before it. – David Schwartz Feb 25 '16 at 18:01