库中的 C++ 类初始化列表导致堆栈损坏

问题描述 投票:0回答:2

我已经将我的问题提炼为一个(希望如此)非常简单的例子。在高层次上,我有一个提供类实现的共享库,以及一个使用该库的主要可执行文件。在我的示例中,然后使用

CPPFLAG=-DMORE
扩展了库,这样类初始化器列表现在就有了一个额外的成员。由于库的 ABI 签名未更改,因此无需重新编译可执行文件。然而,就我而言,我得到了一个核心转储。我不明白为什么这是一个问题。有人可以指出我哪里出错了吗?

环境

Linux, amd64, gcc 版本 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)

设置

使用下面提供的代码,执行以下操作:

  1. make clean
  2. make main
    (它还构建了库的
    base-orig
    版本)
  3. ./main
    运行得很好
  4. make base_more
  5. ./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)
    

代码

库头(base.h)

#ifdef MORE
    #include <functional>
#endif

class base
{
  public:
    base();
    ~base();

  private:

#ifdef MORE
    std::function<void()> more_;
#endif
};

库源(base.cpp)

#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
}

可执行文件(main.cpp)

#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 汇编的了解相当薄弱。

c++ g++ initializer-list ctor-initializer stack-corruption
2个回答
2
投票

与 base.cpp 包含的相同标头相比,main.cpp 中包含的

base.h
标头看起来不同(具有不同的大小),后者此时还定义了
more_
成员。


0
投票

您正在尝试将一个可能为 24 或 32 字节的

std::function
成员放入一个 1 字节的空类中。根本没有足够的空间来容纳它。

当您在

base x;
中说
main
时,
main
会做两件事:

  1. 预留足够的内存来容纳一个
    base
    对象
  2. 将指向该内存的指针传递给
    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
).

© www.soinside.com 2019 - 2024. All rights reserved.