有没有一种方法可以强制C++编译器不优化静态库中特定的静态对象?

4

(只需针对gcc 5.4工作,如果找不到通用解决方案)

我有一个通用工厂,用于根据某个键(例如表示类名的字符串)构造对象。该工厂必须允许注册在构建时可能未知的类(因此我无法简单地显式注册类列表)。

为了注册这些键及其关联的构造函数,我有另一个“RegisterInFactory”(模板化)类。在每个类的源文件中,我构建与该类相应的匿名命名空间中的对象。通过这种方式,一旦构建全局对象,每个类就会自动注册到工厂中。这些对象永远不会在执行此初始注册任务以外使用或引用。

然而,当代码编译成静态库时,当将该库链接到可执行文件时,这些静态对象永远不会被构造,因此类不会向工厂注册,工厂也无法创建任何东西。

我知道-Wl,--whole-archive -lfoo标志,它确实包括这些全局对象。但它还引入了很多'多重定义'错误。我知道还有另一个标志可以关闭多重定义错误,但我不感到放心没有这些错误。 我知道-u symbolName可以从这些多重定义错误中关闭特定的符号名称(至少我认为它是这样做的)。但是,这些冗余函数太多了,不现实(主要来自protobuf类)。

有没有办法告诉编译器不要优化那些对象,而只是这些对象,以避免多重定义问题?是否有其他模式我可能能够遵循符合约束? (特别是我不知道在编译时可能注册到工厂的类。)

简化的示例代码:Factory.h:

template<Base>
class Factory{
  ...
  template<Derived>
  class RegisterInFactory{
    RegisterInFactory(){
      instance().regInFactory(derivedConstructorFunctional);
    }
  };
};

在Derived.cpp文件中:
namespace{ BaseFactory::RegisterInFactory<Derived> registerMe{"Derived"}; }

最终说明:在一定程度上,我有些幸运,即使没有链接器标志,它们仍然会被包含,但唯一的方法似乎是如果派生类“足够”复杂。或者可能是如果我直接在链接的可执行文件中使用派生类。当发生时,我无法确定为什么它能起作用。

1
我认为问题在于命名空间作用域中的 static 直到在该 CPP 文件中的某些代码被使用之前才会被构造。因此,如果从未调用 CPP 文件中的任何代码,则这些 static 可能永远不会被构造。这与 Gcc 优化无关,这是 C++ 的工作原理。MSVC 是一个奇怪的例外,它忽略了这一点,在启动 main 之前构造所有命名空间 static - Mooing Duck
如果您在库模块中引用了某些符号,它将被链接(包括静态对象)。如果模块中没有任何东西的引用,则不会被包含。这就是应该的工作方式。这个问题已经被问过几次了 https://dev59.com/QGYq5IYBdhLWcg3w5Ui8?noredirect=1&lq=1 - Bo Persson
我也浏览了其他几个答案。我的问题是,是否有一种方法可以强制包含特定的对象,而不是整个归档选项,这不仅过度,而且在其过度中存在问题。@BasileStarynkevitch:那可能就是我要找的,我会深入研究一下看看是否适合。 - shavera
如果您只想忽略多个定义错误,可以使用-Wl,--allow-multiple-definition - tmm1
1个回答

4
问题与优化无关,而是与静态库中的符号链接器如何链接有关。
然而,当代码编译成静态库时,将该库链接到可执行文件中时,这些静态对象永远不会被构建,因此类不会向工厂注册,工厂也就无法创建任何东西。
这是因为没有其他变量引用该注册变量。因此,链接器不会从存档中提取符号定义。
要告诉Unix链接器即使没有其他内容引用它也要保留该注册变量,在链接到该静态库时使用-Wl,--undefined=<symbol>编译器开关:
-u symbol --undefined=symbol 强制在输出文件中将symbol输入为未定义符号。这样做可能会触发从标准库链接其他模块。可以重复使用带有不同选项参数的-u以输入其他未定义符号。
如果该注册变量具有“C”连接,则<symbol>是变量名。
对于C ++连接,您需要使用nm --defined-only <object-file>查找搅拌名称。您还可能需要将该变量放入命名空间中,以使其具有外部链接。
示例:
[max@supernova:~/src/test] $ cat mylib.cc
#include <cstdio>

namespace mylib {

struct Register
{
    Register() { std::printf("%s\n", __PRETTY_FUNCTION__); }
};

Register register_me;

}

[max@supernova:~/src/test] $ cat test.cc
#include <iostream>

int main() {
    std::cout << "Hello, world!\n";
}

[max@supernova:~/src/test] $ make
mkdir /home/max/src/test/debug
g++ -c -o /home/max/src/test/debug/test.o -MD -MP -std=gnu++14 -march=native -pthread -W{all,extra,error,inline} -ggdb -fmessage-length=0 -Og test.cc
g++ -c -o /home/max/src/test/debug/mylib.o -MD -MP -std=gnu++14 -march=native -pthread -W{all,extra,error,inline} -ggdb -fmessage-length=0 -Og mylib.cc
ar rcsT /home/max/src/test/debug/libmylib.a /home/max/src/test/debug/mylib.o
g++ -o /home/max/src/test/debug/test -ggdb -pthread /home/max/src/test/debug/test.o /home/max/src/test/debug/libmylib.a

[max@supernova:~/src/test] $ ./debug/test 
Hello, world! <-------- Missing output from mylib::register_me.

[max@supernova:~/src/test] $ nm --defined-only -C debug/mylib.o
0000000000000044 t _GLOBAL__sub_I__ZN5mylib11register_meE
0000000000000000 t __static_initialization_and_destruction_0(int, int)
0000000000000000 B mylib::register_me                        <-------- Need a mangled name for this.
0000000000000000 r mylib::Register::Register()::__PRETTY_FUNCTION__

[max@supernova:~/src/test] $ nm --defined-only debug/mylib.o
0000000000000044 t _GLOBAL__sub_I__ZN5mylib11register_meE
0000000000000000 t _Z41__static_initialization_and_destruction_0ii
0000000000000000 B _ZN5mylib11register_meE                   <-------- The mangled name for that.
0000000000000000 r _ZZN5mylib8RegisterC4EvE19__PRETTY_FUNCTION__

# Added -Wl,--undefined=_ZN5mylib11register_meE to Makefile.
[max@supernova:~/src/test] $ make 
g++ -o /home/max/src/test/debug/test -ggdb -pthread -Wl,--undefined=_ZN5mylib11register_meE /home/max/src/test/debug/test.o /home/max/src/test/debug/libmylib.a

[max@supernova:~/src/test] $ ./debug/test 
mylib::Register::Register() <-------- Output from mylib::register_me as expected.
Hello, world!

这种方法很好。它运行得非常流畅,而且(在我看来)比整个归档方法更少出错。 - templatetypedef
我在测试程序中添加了 -Wl,--undefined = _ZN7animals12_GLOBAL__N_110registerMeE 作为链接器的标记(例如,使用 animals :: [anonymous namespace] :: RegisterInFactory <Cat> registerMe {"cat"}; 作为对象)。那似乎没有包括该对象。我将注册表移到匿名命名空间外 --undefined = _ZN7animals10registerMeB5cxx11E ,但似乎仍然无法工作。我可能需要更深入地研究这个未定义标志以了解问题所在。 - shavera
@MaximEgorushkin 非常感谢!我期待着更多的实验。 - shavera
这个技巧只适用于符号是全局的(即不是“静态”的),并且在nm输出中显示为“B”(而不是“b”)的情况。 - tmm1
1
@tmm1 这不是一个技巧,而是链接工作的方式。静态意味着内部链接,这些符号无法从不同的翻译单元访问。 - Maxim Egorushkin
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接