[CMake] Newbie question: Static linking

Michael Hertling mhertling at online.de
Mon May 23 12:46:15 EDT 2011


On 05/23/2011 05:09 PM, Sanatan Rai wrote:
> On 23 May 2011 16:00, Michael Wild <themiwi at gmail.com> wrote:
>> Everything that relies on static/global initialization to register
>> factories is an implicit scheme. An explicit scheme is where the
>> dependent code (e.g. the main() function) calls a function to do the
>> registration.
> 
>    Ok, got you. However, would this not imply a monolithic build? How is
> main to know that an object of a type belonging to a base class of interest
> exists in linked library?

In order to further clarify things, look at the following project:

# CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8 FATAL_ERROR)
PROJECT(GLOBALS CXX)
SET(CMAKE_VERBOSE_MAKEFILE ON)
ADD_LIBRARY(helper STATIC helper.cxx)
ADD_EXECUTABLE(main main.cxx)
TARGET_LINK_LIBRARIES(main helper)

// main.cxx:
int main(void){return 0;}

// helper.cxx:
#include <iostream>

class helper {};

namespace {

  bool registerhelper(const char *ident, helper *(*creator)())
  {
    std::cout << ident << "=" << (void *)creator << std::endl;
  }

  helper *helperCreator()
  {
    return (new helper());
  }
  const bool helperRegistered = registerhelper("helper", helperCreator);
}

If I'm not mistaken, this is roughly what you do in your project.

Although the main target is linked against libhelper.a, the object file
helper.cxx.o is dropped because none of its entities is referred to by
main.cxx, or in other words: Which reason the linker should have to
include helper.cxx.o in the final binary? To make helper.cxx.o be
included, a single reference from main.cxx usually suffices:

// helper.cxx:
#include <iostream>

int h;  // <-- For external reference.

class helper {};

namespace {

  bool registerhelper(const char *ident, helper *(*creator)())
  {
    std::cout << ident << "=" << (void *)creator << std::endl;
  }

  helper *helperCreator()
  {
    return (new helper());
  }
  const bool helperRegistered = registerhelper("helper", helperCreator);
}

// main.cxx:
extern int h;
int h0 = h;
int main(void){return 0;}

Now, the entities from helper.cxx.o are included in the final binary,
i.e. you'll see the "ident=..." message. Of course, the same happens
when you include helper.cxx.o immediately in the main executable by
mentioning helper.cxx among main's source files, i.e. a monolithic
build. However, be aware that advanced linkers and, in particular,
optimising compiler back-ends may feel free to remove entities that
seem to be unnecessary for the program to run, as David has remarked
in the meantime.

Michael's advice to explicitly register your factory classes means that
there will be an external reference to the concerned object files, so
they'll be included and the registration will be done via the boolean
constants' initialisation, and that's what I'd advise, too. ATM, you
try to have some actions performed in your program without referring
to these actions by any means, and this simply does not work.

Besides, when using --[no-]whole-archive in order to force the linker
to include all of a static library's object files - be aware of the
already mentioned limitations and the fact that you will get *all*
object files, not just the ones you need - you might intersperse
these flags immediately in TARGET_LINK_LIBRARIES() without -L/-l:

target_link_libraries(myTarget
    -Wl,--whole-archive helper1 helper2 -Wl,--no-whole-archive)

>    As I mentioned earlier, I did this a little more explicitly in a
> .NET project in the following
> manner:
> 
>   * I had a factory object, whose job it was to hold creator functions
> for objects of classes
>     derived from a base class of interest.
> 
>  * The factory was a singleton, and had a static method that could be
> called. The method loaded
>    all linked assemblies, and picked out classes that were derived
> from the base class.
> 
>   * Then it explicitly registered the class with the factory.
> 
> This seems to me to be a hybrid between implicit and explicit
> registration. The actual mechanics
> as one might imagine relied heavily on .NET reflection calls. One of
> the ugliest bits of code I have
> ever written.

If the object file holding the factory is placed in a static library,
and if there's no reference to any of this object file's entities from
anywhere, the above-noted approach would also fail on *nix, but you say
"...a static method that could be called.": Calling this method *is* a
reference to the factory's object file from outside, so it would be
included in the final binary, and everything works fine.

In summary, this whole issue is not related to C++ or even to CMake,
but to the manner static libraries are handled: The linker - at least
the GNU one - picks out entire object files, or drops them if they are
not referred to. This is something one must keep in mind, particularly
when dealing with global objects for initialisation purposes. BTW, also
keep in mind that the order of initialisations, i.e. constructor calls,
among global objects is unspecified; this might become important when
such global objects refer to each other.

Regards,

Michael


More information about the CMake mailing list