Can “construct on first use” idiom fail under any

2019-03-29 20:57发布

问题:

I'm building my program (tests actually) using some static library.
This library contains one file inside which I have functions like that:

string& GetString() {
    static string strFilename;
    return strFilename;
}

void PrintToScreen() {
    printf("String: %s\n", GetString().c_str())
}

Then in my main.cpp (outside the library) I'm doing:

GetString() = "abc";
printf("String: %s\n", GetString().c_str());
PrintToScreen();

And I get this output:

String: abc
String:

So looks like second call to the function (but done from different file, which is inside the library) somehow clear previous value, reinitialize it, or uses own copy of it.
I changed GetString function to use 'new' but result is exactly the same (btw. program never crash).
But I don't understand hot it's possible?
Any ideas what I'm doing wrong?

------------------------------- UPDATE ------------------------------

  1. Test is done is single threaded environment.
  2. It works on some platforms and on some it doesn't (works on windows, MacOS and AIX, doesn't work on linux, HP_UX, Solaris, FreeBSD...)
  3. I verified address of the strFilename during the execution (printf inside GetString) and looks like it's one variable without duplicates (address is always the same)
  4. BUT, with nm on the final lib I get something like that:

0000000000000030 T _Z16GetLogprintfFilev
0000000000000008 b _ZGVZ16GetLogprintfFilevE16strLogprintfFile
0000000000000018 b _ZZ16GetLogprintfFilevE16strLogprintfFile
U _Z16GetLogprintfFilev

and with nm on my base lib (used by final lib) I get:

0000000000000030 T _Z16GetLogprintfFilev
0000000000000008 b _ZGVZ16GetLogprintfFilevE16strLogprintfFile
0000000000000018 b _ZZ16GetLogprintfFilevE16strLogprintfFile

回答1:

Yes this is quite possible when static linking.

Example:

 libA.a   // contains GetString()
          // Contains. PrintToScreen()
          // Here the reference has been resolved.

 libX.so  // Contains a call to GetString()
          // This library is linked with libA.a
          // Thus pulls in the function GetString() into this library.

 libY.so  // Contains a call to PrintToScreen()
          // This library is linked with libA.a
          // Thus pulls in the function PrintToScreen and GetString() into this library.

 a.out    // linked against libY.so libX.so
          // This has two distinct versions of GetString()

In the above example if a.out contains a call got getString() it is OS specific which version of getString() will be called. On most systems the load order of the individual shared library is used but on others it will do a depth first search of the shared libraries (ie lib X loads XA XB and Y loads YA YB. search order could be X XA XB Y YA YB or X Y XA XB YA YB). You need to consult each OS shared library documentation to understand how symbols are searched for at runtime.

The solution here is to only link against shared libraries (the default in most situations).
That way you only get one copy of libA (assuming you made libA a shared lib) and its content loaded into runtime only once (with no copies).

Note: This is not a failure at the language level.
This a failure caused by linking which is beyond the scope of the C/C++ language.



回答2:

Actually there was one missing think in example. It should look like this:

string& GetString() {
  static string strFilename;
  return strFilename;
}

extern "C" {
  void PrintToScreen() {
    printf("String: %s\n", GetString().c_str())
  }
}

Strange. Anyway, I refactored it to something like this:

extern "C" {
  string* GetString() {
    static string strFilename;
    return &strFilename;
  }

  void PrintToScreen() {
    printf("String: %s\n", GetString()->c_str())
  }
}

And it works without problem now.
Still it seems strange to me that compiler was not complaining.
Thanks to all for their contribution, problem is solved now.

---------------------------------- EDIT ----------------------------------

I experienced this problem again later so it was not proper fix.
The real problem was some singleton which was initialized
in meantime and had in the class constructor:

GetString() = "";

So, simple problem, but really hard to track...