[英]Linux C++ shared library constructor segfaults in libstdc++
我正在尝试学习和了解如何为 Linux 开发共享库。 但是,我遇到了一个我无法解释的段错误。 我想知道这是否是在共享库中实现构造函数的正确方法。 我的模型库的源代码如下。
助手.h:
#ifndef HELPER_HELPER_H
#define HELPER_HELPER_H
namespace helper {
int GetHelp(int i=0);
} // namespace helper
#endif // HELPER_HELPER_H
助手.cxx:
#include "helper.h"
#include <iostream>
#include <string>
namespace helper {
void __attribute__((constructor)) InitHelp(void)
{
std::cout << "Setting up Helper" << std::endl;
}
void __attribute__((destructor)) FinishHelp(void)
{
std::cout << "Leaving Helper" << std::endl;
}
int GetHelp(int i)
{
return 2*i+1;
}
} // namespace helper
主.cxx:
#include <iostream>
#include <cstdlib>
#include "helper.h"
int main(int argc, char** argv)
{
std::cout << "Hello World" << std::endl;
const int i = 3;
std::cout << "GetHelp returned " << helper::GetHelp(i) << std::endl;
std::cout << "Good bye World" << std::endl;
return EXIT_SUCCESS;
}
我已将它们放入单独的子目录中,并添加了最简单的 cmake 文件。
根目录:
cmake_minimum_required(VERSION 3.19)
project(MyProject VERSION 1.2.3
DESCRIPTION "Very nice project"
LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_subdirectory(libhelper)
add_subdirectory(pdemo)
libhelper 目录:
add_library(helper)
target_sources(helper PUBLIC helper.h
PRIVATE helper.cxx)
target_include_directories(helper PUBLIC .)
install(TARGETS helper)
pdemo 目录:
add_executable(pdemo)
target_sources(pdemo PRIVATE main.cxx)
target_link_libraries(pdemo PRIVATE helper)
install(TARGETS pdemo)
创建构建目录后,设置
cmake -DCMAKE_BUILD_TYPE='Debug' -DBUILD_SHARED_LIBS=ON ..
和编译,可执行文件产生一个段错误。 从核心文件中使用 gdb 进行追溯,结果发现问题出现在 InitHelp function 中,位于 libstdc++ 深处。 By removing the line std::cout << "Setting up...
the problem disappears and the program produces the expected output. I have tested it with both GCC 5.4.0 and GCC 10.2.0 and both suffers the same problem. Oddly ,如果打开 LTO,问题就会消失。
我希望在初始化可执行文件期间调用构造函数时已经设置了所有支持库,包括 C++ 标准库。 是这样吗? 如果是,是什么导致了段错误? 我是否误解了构造函数的使用方式?
(Note: I understand that I could produce the same result by moving the library constructor to the constructor of a C++ class and instantiating a global object from this class. I guess the compiler would use the same mechanism with some additional steps maybe I'm不知道。但是,我想了解发生了什么以及实现共享库的真正规则是什么。)
更新:
在阅读了 KamilCuk 的答案后,我开始思考并得出结论,事情比我以前想象的要糟糕得多。 考虑以下程序:
#include <iostream>
#include <cstdlib>
class Foo
{
public:
Foo() {
std::cout << "This is Foo speaking" << std::endl; }
};
Foo global_foo;
int main(int argc, char** argv)
{
std::cout << "Hello World" << std::endl;
return EXIT_SUCCESS;
}
根据我们之前讨论的内容,这个看似简单的生物是无效的 C++ 代码。 很明显,如果它静态链接到 libstdc++,那么它就是一个 static 初始化命令失败,因为不能保证在 global_foo 之前构造 std::cout 等。 如果它是动态链接的,则无法保证正确的初始化顺序,因为共享对象和可执行文件的加载过程没有定义任何预定的库初始化完成顺序,并且库构造函数在加载过程的这个阶段运行保证不会初始化其他库。 实际上,这意味着 static 初始化命令惨败过度扩展了链接单元(.so 和可执行文件)的边界。
从实际角度来看,这意味着在 C++ 程序中实例化 static 对象必须非常小心。 开发人员必须确保构造函数中直接或间接发生的任何事情都不会触及此 class 控制之外的任何内容,不会触及其他库中的设施,不涉及全局变量等。保守的,最好的方法是完全避免将对象作为全局变量。 (我知道,避免全局变量总是很重要的,但有时一些例外是必要的。像 std::cout :) )
此外,为 C 或 C++ 设计共享库时,库构造函数不能依赖库所依赖的任何其他库。
我理解的好吗? 顺便说一句,MS Windows dll 是否具有相同的属性?
是这样吗?
不。
这是怎么回事
std::cout
在您的库之后初始化,仅此而已。
什么是真正的规则
只是不要在构造函数中使用全局变量或确保首先构造这些对象。 研究“静态初始化命令惨败”。
如果我在设置时不能依赖我的库所依赖的任何库,如果可能需要调用这些库,我该如何设置自己的库?
确保您依赖的所有对象都已初始化。
在全局std::cout
标准流的情况下,有一个std::ios_base::Init
。
也不要在不需要时使用不可移植的扩展。 __attribute__((__constructor__))
和__attribute__((__destructor__))
用于 C 代码以挂钩到启动/关闭。 只需使用完全相同的实际 C++ 语法。
struct InitHelper {
InitHelper() {
std::ios_base::Init some_name; // make sure std::cout is initialized
std::cout << "Setting up Helper" << std::endl;
}
~InitHelper() {
std::cout << "Leaving Helper" << std::endl;
}
};
static InitHelper inithelper; // will call constructor and destructor
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.