我看到 LTO 优化了某个 TU 中的一些全局对象,如果该 TU 中没有明确来自另一个 TU 的函数。
以下摘录尝试描述所涉及的关键类和文件(请注意,这仅用于演示目的,可能并不完全准确):
我有一个单例类,它维护已构造的Registrar所有类型对象的列表。Foo为了避免静态构造顺序失败,我在构造第一个 Foo 类型的对象时动态构造该对象的实例。
// Registrar.hpp
class Registrar
{
public:
static Registrar * sRegistrar;
std::vector<Foo *> objectList;
Registrar() = default;
};
Run Code Online (Sandbox Code Playgroud)
接下来,我们上课了Foo。此类的实例Registrar如上所述进行注册。
// Foo.hpp
class Foo
{
public:
Foo()
{
if (Registrar::sRegistrar == nullptr)
Registrar::sRegistrar = new Registrar();
Registrar::sRegistrar->objectList.push_back(this);
}
};
Run Code Online (Sandbox Code Playgroud)
的实例Foo是可以从多个文件创建的全局变量。在一个这样的文件中,我们碰巧定义了另一个从其他地方调用的函数:
// file1.hpp
void someFunctionThatIsCalledExplicitly()
{
doSomething();
}
namespace
{
__attribute__((used, retain))
Foo f1;
}
Run Code Online (Sandbox Code Playgroud)
但在另一个文件中,我们只是创建了一个实例Foo:
// file2.hpp
namespace
{
__attribute__((used, retain))
Foo f2;
}
Run Code Online (Sandbox Code Playgroud)
我看到的是f2正在优化,而f1没有,尽管添加了__attribute__((used, retain))class 的所有声明Foo。
我应该如何防止 LTO 优化这些实例?为什么属性没有区别?
编辑:我能够编写一个小示例来重现所述问题。
#include <iostream>
#include "Registrar.hpp"
#ifdef FORCE_LINKAGE
extern int i;
#endif
extern void someFunctionThatIsCalledExplicitly();
int main()
{
#ifdef FORCE_LINKAGE
i++;
#endif
someFunctionThatIsCalledExplicitly();
if (Registrar::sRegistrar == nullptr)
{
std::cout << "No instances of foo";
}
else
{
std::cout << Registrar::sRegistrar->objectList.size() << " instances of foo\n";
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
#pragma once
class Foo
{
public:
Foo();
};
Run Code Online (Sandbox Code Playgroud)
#include "Foo.hpp"
#include "Registrar.hpp"
Foo::Foo()
{
if (Registrar::sRegistrar == nullptr)
{
Registrar::sRegistrar = new Registrar();
}
Registrar::sRegistrar->objectList.push_back(this);
}
Run Code Online (Sandbox Code Playgroud)
#pragma once
#include <vector>
#include "Foo.hpp"
class Registrar
{
public:
static Registrar * sRegistrar;
std::vector<Foo *> objectList;
Registrar() = default;
};
Run Code Online (Sandbox Code Playgroud)
#include "Registrar.hpp"
Registrar * Registrar::sRegistrar = nullptr;
Run Code Online (Sandbox Code Playgroud)
#include <iostream>
#include "Foo.hpp"
void someFunctionThatIsCalledExplicitly()
{
std::cout << "someFunctionThatIsCalledExplicitly() called\n";
}
namespace
{
__attribute__((used, retain))
Foo f1;
}
Run Code Online (Sandbox Code Playgroud)
#include "Foo.hpp"
#ifdef FORCE_LINKAGE
int i = 0;
#endif
namespace
{
__attribute__((used, retain))
Foo f2;
}
Run Code Online (Sandbox Code Playgroud)
CC = clang++
LIBTOOL = libtool
BUILDDIR = build
BINFILE = lto
BUILDFLAGS = -flto -std=c++17
LINKFLAGS = -flto
.PHONY: all
all: $(BUILDDIR) $(BINFILE)
.PHONY: force
force: def all
.PHONY: def
def:
$(eval BUILDFLAGS += -DFORCE_LINKAGE)
$(BINFILE): foo files
$(CC) -o $(BUILDDIR)/$@ $(LINKFLAGS) -L$(BUILDDIR) $(addprefix -l, $^)
foo: Foo.o main.o Registrar.o
$(LIBTOOL) $(STATIC) -o $(BUILDDIR)/lib$@.a $(addprefix $(BUILDDIR)/, $^)
files: File1.o File2.o
$(LIBTOOL) $(STATIC) -o $(BUILDDIR)/lib$@.a $(addprefix $(BUILDDIR)/, $^)
%.o: %.cpp
$(CC) $(BUILDFLAGS) -c -o $(addprefix $(BUILDDIR)/, $@) $<
.PHONY: $(BUILDDIR)
$(BUILDDIR):
mkdir -p $(BUILDDIR)
.PHONY: clean
clean:
rm -rf $(BUILDDIR)
Run Code Online (Sandbox Code Playgroud)
我有两种变体,一种与上面类似(我只看到 1 个实例),另一种是通过声明我在其他地方引用的全局变量来强制链接(在这里我看到两个实例):
$ make
$ ./build/lto
someFunctionThatIsCalledExplicitly() called
1 instances of foo
$ make force
$ ./build/lto
someFunctionThatIsCalledExplicitly() called
2 instances of foo
Run Code Online (Sandbox Code Playgroud)
好吧,我做了一些挖掘,事实上你链接的 .a 库才是罪魁祸首,而不是 LTO,也不是任何其他优化。
顺便说一句,这已经在 SO 上提出了,请参阅:Static初始化和静态库全局变量的销毁不会发生在g ++中
当链接 .o 文件时(就像我在 godbolt 上所做的那样),一切都会进入并且可以正常工作。
对于 .a 文件,仅链接引用的代码,其余部分则不链接。创建虚拟变量是一种解决方法,但正确的方法是将其传递--whole-archive给链接器。
由于 libtool 问题,我无法运行基于 makefile 的示例,但请查看我的 CMake 配置:
cmake_minimum_required(VERSION 3.18)
project(LINK)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
add_library(Files File1.cpp File2.cpp)
target_include_directories(Files
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_compile_definitions(Files PUBLIC ${FORCE})
add_executable(test Foo.cpp main.cpp Registrar.cpp)
# note the line below
target_link_libraries(test -Wl,--whole-archive Files -Wl,--no-whole-archive)
target_compile_definitions(test PUBLIC ${FORCE})
Run Code Online (Sandbox Code Playgroud)
链接时,它将按以下方式调用命令:
g++ -o test -Wl, --whole-archive -l:libFiles.a -Wl, --no-whole-archive Foo.o Registrar.o main.o