发送具有 std::vector 成员的结构时出现分段错误

Posted

技术标签:

【中文标题】发送具有 std::vector 成员的结构时出现分段错误【英文标题】:Segmentation fault when sending struct having std::vector member 【发布时间】:2017-02-25 10:14:30 【问题描述】:

为什么我使用mpirun -np 2 ./out 命令得到以下代码的以下错误?我在调整std::vector 的大小后调用了make_layout(),所以通常我不应该收到这个错误。如果我不调整大小,它会起作用。是什么原因?

ma​​in.cpp:

#include <iostream>
#include <vector>
#include "mpi.h"

MPI_Datatype MPI_CHILD;

struct Child

    std::vector<int> age;

    void make_layout();
;

void Child::make_layout()

    int nblock = 1;
    int age_size = age.size();
    int block_count[nblock] = age_size;
    MPI_Datatype block_type[nblock] = MPI_INT;
    MPI_Aint offset[nblock] = 0;
    MPI_Type_struct(nblock, block_count, offset, block_type, &MPI_CHILD);
    MPI_Type_commit(&MPI_CHILD);


int main()

    int rank, size;

    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);    

    Child kid;
    kid.age.resize(5);
    kid.make_layout();
    int datasize;
    MPI_Type_size(MPI_CHILD, &datasize);
    std::cout << datasize << std::endl; // output: 20 (5x4 seems OK).

    if (rank == 0)
    
        MPI_Send(&kid, 1, MPI_CHILD, 1, 0, MPI_COMM_WORLD);
    

    if (rank == 1)
    
        MPI_Recv(&kid, 1, MPI_CHILD, 0, 0, MPI_COMM_WORLD, NULL);
    

    MPI_Finalize();

    return 0;

错误信息:

*** Process received signal ***
Signal: Segmentation fault (11)
Signal code: Address not mapped (1)
Failing at address: 0x14ae7b8
[ 0] /lib/x86_64-linux-gnu/libpthread.so.0(+0x113d0)[0x7fe1ad91c3d0]
[ 1] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x22)[0x7fe1ad5c5a92]
[ 2] ./out[0x400de4]
[ 3] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fe1ad562830]
[ 4] ./out[0x400ec9]
*** End of error message ***

【问题讨论】:

这可能是我给过的最糟糕的与 MPI 相关的建议,但你可以重载一元 Child::operator&amp; 以返回 age.data() int nblock = 1; 应该是const int nblock = 1; @M.M 没有任何区别。 @HristoIliev 如果age 不是struct 的第一个成员怎么办? 那么age.data()在构造的MPI数据类型中的偏移量应该是相对于结构的第一个成员的绝对地址。这适用于结构的所有其他成员。或者您可以简单地使用绝对地址作为偏移量,并在MPI_Send / MPI_Recv 中指定MPI_BOTTOM 作为缓冲区地址。 【参考方案1】:

这是一个示例,其中有几个 std::vector 成员使用具有绝对地址的 MPI 数据类型:

struct Child

    int foo;
    std::vector<float> bar;
    std::vector<int> baz;

    Child() : dtype(MPI_DATATYPE_NULL) 
    ~Child()  if (dtype != MPI_DATATYPE_NULL) MPI_Type_free(dtype); 

    const MPI_Datatype mpi_dtype();
    void invalidate_dtype();

private:
    MPI_Datatype dtype;
    void make_dtype();
;

const MPI_Datatype Child::mpi_dtype()

    if (dtype == MPI_DATATYPE_NULL)
        make_dtype();
    return dtype;


void Child::invalidate_dtype()

    if (dtype != MPI_DATATYPE_NULL)
        MPI_Datatype_free(&dtype);


void Child::make_dtype()

    const int nblock = 3;
    int block_count[nblock] = 1, bar.size(), baz.size();
    MPI_Datatype block_type[nblock] = MPI_INT, MPI_FLOAT, MPI_INT;
    MPI_Aint offset[nblock];
    MPI_Get_address(&foo, &offset[0]);
    MPI_Get_address(&bar[0], &offset[1]);
    MPI_Get_address(&baz[0], &offset[2]);

    MPI_Type_struct(nblock, block_count, offset, block_type, &dtype);
    MPI_Type_commit(&dtype);

该类的示例使用:

Child kid;
kid.foo = 5;
kid.bar.resize(5);
kid.baz.resize(10);

if (rank == 0)

    MPI_Send(MPI_BOTTOM, 1, kid.mpi_dtype(), 1, 0, MPI_COMM_WORLD);


if (rank == 1)

    MPI_Recv(MPI_BOTTOM, 1, kid.mpi_dtype(), 0, 0, MPI_COMM_WORLD, NULL);

注意使用MPI_BOTTOM 作为缓冲区地址。 MPI_BOTTOM 指定地址空间的底部,在具有平坦地址空间的架构上为 0。由于传递给MPI_Type_create_struct 的偏移量是结构成员的绝对地址,因此当它们被添加到0 时,结果又是每个结构成员的绝对地址。 Child::mpi_dtype() 返回特定于该实例的延迟构造的 MPI 数据类型。

由于resize() 重新分配内存,这可能导致数据被移动到内存中的不同位置,所以应该使用invalidate_dtype() 方法在resize() 或任何其他操作之后强制重新创建 MPI 数据类型可能会触发内存重新分配:

// ...
kid.bar.resize(100);
kid.invalidate_dtype();
// MPI_Send / MPI_Recv

请原谅以上任何草率的 C++ 代码。

【讨论】:

太棒了。如果结构/类中存在 STL 容器,这是否总是可行的方法?我搜索了人们如何发送包括 STL 容器的类,但找不到任何东西。他们只展示了如何单独发送容器。 这仅适用于将元素存储在连续内存中的容器。它不适用于链接列表或集合。有关使用 MPI 传递 C++ 对象的更通用方法,您应该查看boost.MPI。它有一个相当通用的序列化机制,支持复杂的数据结构。【参考方案2】:

这里的问题是您告诉 MPI 从&amp;kid 发送一个整数块,但这不是您的数据所在的位置。 &amp;kid 指向一个 std::vector 对象,该对象有一个指向分配在堆上某处的整数块的内部指针。

&amp;kid 替换为kid.age.data(),它应该可以工作。当您不调整大小时它“起作用”的原因是向量的大小为 0,因此 MPI 将尝试发送空消息并且不会发生实际的内存访问。

【讨论】:

编译器抱怨“‘struct Child’没有名为‘data’的成员”。 @Shibli : 你需要序列化 ​​kid.age.data() 而不是&amp;kid @ildjarn 是对的,我的意思是kid.age.data()。已更正。 struct 通常包含其他成员,所以我不想只发送std::vector,而是发送整个struct【参考方案3】:

小心,您遇到了几个问题。

首先 std::vector 将对象存储在堆中,因此数据并没有真正存储在您的结构中。

第二您甚至无法在动态库之间发送 STL 容器,对于应用程序实例也是如此。因为它们可能使用不同版本的 STL 编译,并且在不同架构上的工作方式不同。

这是关于这部分问题的好答案:https://***.com/a/22797419/440168

【讨论】:

第二部分不适用于本题。 OP 正在定义一个 MPI 数据类型,该数据类型映射到在内存中连续存储的整数类型的 age.size() 元素序列,这正是 std::vector&lt;int&gt; 的含义。使用 MPI 作为中间件,它不仅可以在随机进程(或您所说的应用程序实例)之间工作,还可以在不同架构上的进程之间工作(如果 MPI 实现支持异构环境)。

以上是关于发送具有 std::vector 成员的结构时出现分段错误的主要内容,如果未能解决你的问题,请参考以下文章

从 dll 导出 std::vector 时出现链接错误

bcc32:专注于`std::vector<bool>`时出现奇怪的错误

具有成员 std::vector 的移动语义

添加向量时出现分段错误。 (C++)

使用 std::count_if() 时出现错误“没有从 'std::vector<double, std::allocator<double> >' 到 'double *'

删除时出现分段错误