我看到LTO从TU中优化一些全局对象,如果该TU中没有显式地来自另一个TU的函数。
下面的摘录试图描述所涉及的关键类和文件(请注意,这只是为了演示目的,可能在所有地方都不完全准确):
我有一个单例类Registrar
,它维护一个包含已构造的Foo
类型的所有对象的列表。为了避免静态构造顺序的失败,我在构造第一个类型Foo的对象时动态地构造这个对象的实例。
// Registrar.hpp
class Registrar
{
public:
static Registrar * sRegistrar;
std::vector<Foo *> objectList;
Registrar() = default;
};
接下来,我们有类Foo
。这个类的实例如上所述在Registrar
中注册。
// Foo.hpp
class Foo
{
public:
Foo()
{
if (Registrar::sRegistrar == nullptr)
Registrar::sRegistrar = new Registrar();
Registrar::sRegistrar->objectList.push_back(this);
}
};
Foo
的实例是全局的,可以从几个文件创建。在一个这样的文件中,我们碰巧定义了另一个函数,可以从其他地方调用:
// file1.hpp
void someFunctionThatIsCalledExplicitly()
{
doSomething();
}
namespace
{
__attribute__((used, retain))
Foo f1;
}
但是在另一个文件中,我们只创建了一个Foo
的实例:
// file2.hpp
namespace
{
__attribute__((used, retain))
Foo f2;
}
我所看到的是f2
正在得到优化,而f1
则没有,尽管为Foo
类的所有声明添加了__attribute__((used, retain))
。
我应该如何防止LTO优化出这些实例?为什么属性没有区别?
编辑:我可以写一个小例子来重现上述问题。
- main.cpp:
#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 foon";
}
return 0;
}
- Foo.hpp
- Foo.cpp:
- Registrar.hpp:
- Registrar.cpp:
- File1.cpp:
- File2.cpp:
- Makefile:
#pragma once
class Foo
{
public:
Foo();
};
#include "Foo.hpp"
#include "Registrar.hpp"
Foo::Foo()
{
if (Registrar::sRegistrar == nullptr)
{
Registrar::sRegistrar = new Registrar();
}
Registrar::sRegistrar->objectList.push_back(this);
}
4#pragma once
#include <vector>
#include "Foo.hpp"
class Registrar
{
public:
static Registrar * sRegistrar;
std::vector<Foo *> objectList;
Registrar() = default;
};
#include "Registrar.hpp"
Registrar * Registrar::sRegistrar = nullptr;
#include <iostream>
#include "Foo.hpp"
void someFunctionThatIsCalledExplicitly()
{
std::cout << "someFunctionThatIsCalledExplicitly() calledn";
}
namespace
{
__attribute__((used, retain))
Foo f1;
}
#include "Foo.hpp"
#ifdef FORCE_LINKAGE
int i = 0;
#endif
namespace
{
__attribute__((used, retain))
Foo f2;
}
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)
我有两个版本,一个类似于以上(我只看到一个实例),另一个在我力连杆通过声明一个全局变量,我指的是其他地方(这里我看到两个实例):
$ make
$ ./build/lto
someFunctionThatIsCalledExplicitly() called
1 instances of foo
$ make force
$ ./build/lto
someFunctionThatIsCalledExplicitly() called
2 instances of foo
好吧,我做了一些挖掘,事实上你链接的。a库是这里的罪魁祸首,不是LTO,也不是任何其他优化。
这在SO之前已经提到过,顺便说一下,参见:静态库的静态初始化和销毁's全局变量在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})
链接时,它将以以下方式调用命令:
g++ -o test -Wl, --whole-archive -l:libFiles.a -Wl, --no-whole-archive Foo.o Registrar.o main.o