1

Introduction

I have a class (EagerSingleton) that is written in eager-loaded singleton pattern (class is instantiated in the source file). Class is located in a library my_lib (see Example of the CMake build system generator). The class instantiates itself and is meant to run in the background, without any user interaction.

Expected behavior

Instance is instantiated and the onRun() (see example of the eager-loaded singleton used) member function is called after the FreeRTOS scheduler is called.

Realized behavior

Looking into the final executable (.elf), there are no references to the EagerSingleton. Furthermore, after the FreeRTOS scheduler is started, other parts of the application start working, but not this singleton. It should also be noted, inspecting the static library libmy_lib.a, that EagerSingleton is compiled into the libmy_lib.a, but is obviously omitted from the final executable.

Additional information

This project also has a makefile build system that works perfectly with this software design. My guess is, since makefile system builds everything together (and doesn't separate everything into libraries) it doesn't optimize the class, where the CMake sees that this class is not referenced anywhere and for that reason it removes it.

Question

Is this behavior expected? Best way to fix this issue?

Example of the eager-loaded singleton used

// eager_singleton.hpp
// This class is only for the problem demonstration!
#pragma once

class EagerSingleton
{
public:
    EagerSingleton getInstance()
    {
        return instance;
    }
private:
    static EagerSingleton instance;
    EagerSingleton();
    void onRun();
};

// eager_singleton.cpp
// This class is only for the problem demonstration!
#include "eager_singleton.hpp"

EagerSingleton EagerSingleton::instance {};

EagerSingleton::EagerSingleton() : xTaskCreateStatic(...) // Passing a member function onRun().
{}

void onRun()
{
    // Do stuff.
}

It must be noted that, with this design, no code referencing this class is needed in the main application.

Example of the CMake build system generator

# <Project root>/lib/CMakeLists.txt

# Library containing eager_singleton.

add_library(my_lib)

target_sources(my_lib PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}/src/eager_singleton.cpp)

target_include_directories(my_lib PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/include)
# <Project root>/CMakeLists.txt
cmake_minimum_required(VERSION 3.16.3)
project(test_project)

set(CMAKE_CXX_STANDARD 20)

add_executable(test_project main.cpp)

add_subdirectory(lib)

target_link_libraries(test_project PRIVATE
        my_lib)

target_compile_options(test_project PRIVATE
        -Wall
        -Wextra
        -pedantic)
Dino Saric
  • 107
  • 7
  • On most embedded toolchains you'll be able to provide "forced linking" of objects as part of the linker script. Also make sure you are using a "standard" project setup and not a "minimal"/"fast" non-standard one, because the latter exclude default constructors from the CRT, since you normally don't want a bunch of constructors slowly down MCU start-up. – Lundin May 27 '21 at 18:55
  • Alternatively you could just manually call the constructor either from the reset vector or start of main(), unless that's too easy :) – Lundin May 27 '21 at 18:56

1 Answers1

1

Explanation:

A Translation Unit has to be used directly or indirectly by the program to be included. Having init side-effects is not enough, so the code is simply not part of the program.

It's a (frankly annoying and counterintuitive) detail that makes all kinds of self-registration features harder to implement, but it really helps with limiting executable bloat when linking against massive libraries.

Ways to fix it:

  1. Preferably: Use compiler-specific features such as __attribute__((used)) to force the inclusion.

For the sake of completeness, here's a few other methods that also work in a compiler-agnostic way:

  1. Have some placeholder symbol (e.g. an empty function, or simple variable) present in the offending translation unit that is referred to by the main program, and eventually optimized away during LTO.
  2. Make the library dynamic. That turns the library itself into an "executable".
  3. Do the evil thing and #include the cpp file instead of linking against it. Generally a bad idea, but if you have a bunch sources that only interact with the program via self-registration, it can be useful to batch them together and use option 2) to load them all from a single placeholder symbol.
  • Also: `static EagerSingleton instance __attribute__((used)) ;`. Much simpler. – Clifford May 27 '21 at 15:59
  • @Clifford That works even accross static libraries? I guess that makes sense. Thanks! –  May 27 '21 at 16:02
  • Good ides, I tried to use the `static EagerSingleton instance __attribute__((used));` in hpp file and/or `EagerSingleton EagerSingleton::instance{} __attribute__((used));` in the cpp file, but, unfortunately, they do not work. My embedded platform doesn't support dynamic libraries, so that option is also out of the windows. I will try other options, even though they make the class non-self contained. – Dino Saric May 27 '21 at 17:18
  • More about aforementioned experiment [here](https://stackoverflow.com/questions/18731489/attribute-used-has-no-effect-when-linking-static-library-into-shared-obje), and [here](https://stackoverflow.com/questions/3549432/preventing-functions-from-being-stripped-from-a-static-library-when-linked-into/3559733#3559733). – Dino Saric May 27 '21 at 17:36