含虚析构函数的自定义MPI类型为何引发段错误?
Boost.MPI发送/接收含虚析构函数的派生类vector触发段错误的原因与解决方法
问题描述
基于Boost.MPI类层次序列化示例修改代码后发现:只要给基类animal添加虚析构函数,接收std::vector<bird>(bird是animal的派生类)时就会触发段错误。类中无指针成员,所有成员均为静态定义,理论上默认析构函数完全适用。
编译命令:
mpic++ mpidatatype.cpp -o out -lboost_mpi -lboost_serialization
示例代码:
#include <boost/mpi.hpp> #include <boost/serialization/base_object.hpp> #include <boost/archive/text_oarchive.hpp> #include <boost/archive/text_iarchive.hpp> #include <boost/serialization/vector.hpp> #include <iostream> using namespace boost::archive; std::stringstream ss; class animal { private: friend class boost::serialization::access; template <typename Archive> void serialize(Archive &ar, const unsigned int version) { ar & legs_; ar& arms_; } protected: int legs_; int arms_; public: animal() = default; animal(const int legs) : legs_{legs},arms_{2} {} int legs() const { return legs_; } virtual void printName() = 0; virtual ~animal()=default; }; class bird : public animal { public: bird() = default; bird(int legs, bool can_fly) : animal{legs}, can_fly_{can_fly} {} bool can_fly() const { return can_fly_; } void printName() { std::cout<<"My name is bird"<<std::endl; }; virtual ~bird()=default; private: friend class boost::serialization::access; template <typename Archive> void serialize(Archive &ar, const unsigned int version) { ar & boost::serialization::base_object<animal>(*this); ar & can_fly_; } bool can_fly_; }; namespace boost { namespace mpi { template <> struct is_mpi_datatype<bird> : mpl::true_ {}; }} int main(int argc, char* argv[]) { boost::mpi::environment env; boost::mpi::communicator world; bool isBirdMPIType = boost::mpi::is_mpi_datatype<bird>::value; std::cout << "Is bird a valid MPI datatype? " << std::boolalpha << isBirdMPIType << std::endl; if (world.size() > 1) { if (world.rank() == 0) { std::vector<bird> dummy ={bird (1,0), bird(2,1)}; world.send(1, 0, dummy); } if (world.rank() == 1) { std::vector<bird> dummy(2, bird(1,1)); world.recv(0, 0, dummy); } } }
原因分析
- 核心问题在于你特化了
boost::mpi::is_mpi_datatype<bird>为mpl::true_,但含虚函数(包括虚析构函数)的类会引入虚函数表指针(vptr),这会改变类的内存布局。而Boost.MPI的is_mpi_datatype要求类型必须是**可平凡复制(trivially copyable)**的类型——即内存布局固定,可直接按字节拷贝,无需序列化逻辑。 - 当类存在虚析构函数时,对象内存中会多出一个vptr成员,这个指针的地址仅在当前进程有效(不同进程的虚表地址独立)。直接通过MPI字节拷贝传输后,接收端对象的vptr指向了发送端进程的虚表地址,后续调用析构函数(比如vector销毁元素时)会访问非法内存,触发段错误。
- 即便类中没有显式指针成员,虚函数的存在已经让类不再满足MPI原生数据类型的要求。
解决方法
- 移除
boost::mpi::is_mpi_datatype<bird>的特化:让Boost.MPI自动使用Boost.Serialization来完成对象的序列化/反序列化,而非直接按字节拷贝。Boost.Serialization会正确处理虚函数相关的内存结构,不会传输无效的vptr,而是在接收端重新构造对象的虚表指针。 - 确保序列化逻辑正确:基类和派生类的
serialize函数需正确实现,派生类必须通过boost::serialization::base_object序列化基类部分。
修改后的关键代码段(删除特化部分):
// 移除以下代码块 /* namespace boost { namespace mpi { template <> struct is_mpi_datatype<bird> : mpl::true_ {}; }} */
内容的提问来源于stack exchange,提问作者MA19
相关产品推荐
相关产品推荐

