我已经将我的问题提炼为一个(希望如此)非常简单的例子。在高层次上,我有一个提供类实现的共享库,以及一个使用该库的主要可执行文件。在我的示例中,然后使用
CPPFLAG=-DMORE
扩展了库,这样类初始化器列表现在就有了一个额外的成员。由于库的 ABI 签名未更改,因此无需重新编译可执行文件。然而,就我而言,我得到了一个核心转储。我不明白为什么这是一个问题。有人可以指出我哪里出错了吗?
Linux, amd64, gcc 版本 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
使用下面提供的代码,执行以下操作:
make clean
make main
(它还构建了库的base-orig
版本)./main
运行得很好make base_more
./main
崩溃
Base hello
Base class constructor has non-null MORE
Base goodbye
Base class destructor has non-null MORE
*** stack smashing detected ***: terminated
Aborted (core dumped)
#ifdef MORE
#include <functional>
#endif
class base
{
public:
base();
~base();
private:
#ifdef MORE
std::function<void()> more_;
#endif
};
#include "base.h"
#include <iostream>
#ifdef MORE
void hi()
{
std::cout << "Hello from MORE" << std::endl;
}
#endif
base::base()
#ifdef MORE
: more_(std::bind(&hi))
#endif
{
std::cout << "Base hello " << std::endl;
#ifdef MORE
if (nullptr != more_)
{
std::cout << "Base class constructor has non-null MORE" << std::endl;
}
#endif
}
base::~base()
{
std::cout << "Base goodbye " << std::endl;
#ifdef MORE
if (nullptr != more_)
{
std::cout << "Base class destructor has non-null MORE" << std::endl;
}
#endif
}
#include "base.h"
int main()
{
base x;
}
base_orig:
g++ -O0 -g -fPIC -shared -olibbase.so base.cpp
objdump -C -S -d libbase.so > orig.objdump
base_more:
g++ -O0 -g -DMORE -fPIC -shared -olibbase.so base.cpp
objdump -C -S -d libbase.so > more.objdump
main: base_orig
g++ -O0 -g -Wextra -Werror main.cpp -o main -L. -Wl,-rpath=. -lbase
objdump -C -S -d main > main.objdump
clean:
rm -f main libbase.so
我试图通过
objdump
输出来找出堆栈被破坏的原因,但是,唉,我对 amd64 汇编的了解相当薄弱。
与 base.cpp 包含的相同标头相比,main.cpp 中包含的
base.h
标头看起来不同(具有不同的大小),后者此时还定义了 more_
成员。
您正在尝试将一个可能为 24 或 32 字节的
std::function
成员放入一个 1 字节的空类中。根本没有足够的空间来容纳它。
当您在
base x;
中说main
时,main
会做两件事:
base
对象base
的构造函数由于在编译
MORE
时没有定义main
,所以就它而言,base
没有数据成员。因此它只会保留 1 个字节的内存(因为每个对象都需要一个唯一的地址,即使它是空的)。然后它将指向该 1 字节内存的指针传递给 base
的构造函数,该构造函数位于您的动态加载库中。由于 MORE
是在编译该库时定义的,它认为一个 base
对象有一个
std::function
成员,并将尝试在
main
传递给它的指针指向的内存中初始化该成员。那里没有足够的空间,所以它最终在被其他东西使用的内存中初始化
more_
。记住,一个指针不包含关于它指向的地方有多少可用内存的信息,所以
base
的构造函数必须假设它被传递了一个指向足够内存的指针来容纳一个
base
对象。这意味着
main
和
base
的构造函数需要就
base
对象的大小达成一致。
也就是说,你可以制作
base
的构造函数
private
并添加一个静态函数
std::unique_ptr<base> make_base()
。这样,为
base
对象分配内存就成为库的唯一责任,并且您永远不会遇到主程序和库不同意需要多少内存来保存
base
的情况。当然,这确实会带来一些开销,因为它需要动态分配所有
base
对象。同样重要的是要确保主程序和库是使用相同的编译器和 C++ 标准库编译的,这样你就可以让它们就你do 跨越库边界传递的任何标准库类型有多大达成一致(例如
std::unique_ptr
或
std::string
).