You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MPI发送含动态数组的结构体报错,求排查与解决方法

MPI发送含动态数组的结构体触发段错误排查与解决

原代码与问题

用户定义的结构体:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <mpi.h>

typedef struct Message
{
    int elemNr;
    char *elem;
} Msg;

原MPI通信代码:

int main(int argc, char **argv) {
    int size, rank;
    int i;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (size < 2) {
        fprintf(stderr,"Requires at least two processes.\n");
        exit(-1);
    }

    // just for simplicity
    const int n = 5;

    // create a new type for struct message
    MPI_Datatype myType;
    Msg msgSnd;

    int block_length[2] = {1, n};

    MPI_Aint elemNrAddr, elemAddr;
    MPI_Aint displacement[2] = {0, 0};
    MPI_Get_address(&msgSnd.elemNr, &elemNrAddr);
    MPI_Get_address(&msgSnd.elem, &elemAddr);

    // just displacement[1] because displacement[0] starts from 0
    displacement[1] = elemAddr - elemNrAddr;
    
    MPI_Datatype types[2] = {MPI_INT, MPI_CHAR};

    MPI_Type_create_struct(2, block_length, displacement, types, &myType);
    MPI_Type_commit(&myType);

    // populate the message
    msgSnd.elemNr = n;
    msgSnd.elem = malloc(sizeof(char) * msgSnd.elemNr);

    srand(time(NULL));
    for (i = 0; i < msgSnd.elemNr; i++)
        msgSnd.elem[i] = rand() % 26 + 'a';

    if (rank != 0) {
        printf("I'm sending\n");
        MPI_Send(&msgSnd, 1, myType, 0, 0, MPI_COMM_WORLD);
        printf("I sent\n");
    } else {
        MPI_Status status;
        Msg msgRcv;
  
        printf("I'm receiving\n");
        MPI_Recv(&msgRcv, 1, myType, 1, 0, MPI_COMM_WORLD, &status);
        printf("I received\n");

        for (i = 0; i < msgRcv.elemNr; i++)
            printf("element %d: %c\n", i, msgRcv.elem[i]);
    
        if (msgRcv.elem != NULL)
            free(msgRcv.elem);        
    }

    if (msgSnd.elem != NULL)
        free(msgSnd.elem);

    MPI_Type_free(&myType);
    MPI_Finalize();

    return 0;
}

运行后进程0接收时elem指针为空,触发段错误。

错误原因

你创建的MPI自定义类型存在核心问题:

  • 结构体中的elem是指针类型,你在block_length里指定为n个MPI_CHAR,但MPI实际发送的是msgSnd.elem这个指针变量的内存地址值,而不是指针指向的数组内容。
  • 接收端拿到的是发送端进程的内存地址,这个地址在接收进程的地址空间是无效的,所以msgRcv.elem要么是未初始化的空值,要么是野指针,访问时直接触发段错误。

解决方法

方法1:分步发送(最直观可靠)

先发送数组长度elemNr,再发送数组内容;接收时先接收长度,再分配内存,最后接收数组:

修正后的代码:

int main(int argc, char **argv) {
    int size, rank;
    int i;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (size < 2) {
        fprintf(stderr,"Requires at least two processes.\n");
        exit(-1);
    }

    const int n = 5;
    Msg msgSnd;
    Msg msgRcv;

    // 初始化发送消息
    if (rank != 0) {
        msgSnd.elemNr = n;
        msgSnd.elem = malloc(sizeof(char) * msgSnd.elemNr);
        srand(time(NULL) + rank); // 加rank避免进程随机数重复
        for (i = 0; i < msgSnd.elemNr; i++)
            msgSnd.elem[i] = rand() % 26 + 'a';

        // 分步发送:先发送长度,再发送数组
        printf("I'm sending\n");
        MPI_Send(&msgSnd.elemNr, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
        MPI_Send(msgSnd.elem, msgSnd.elemNr, MPI_CHAR, 0, 1, MPI_COMM_WORLD);
        printf("I sent\n");
    } else {
        MPI_Status status;
        printf("I'm receiving\n");
        // 分步接收:先接收长度,分配内存,再接收数组
        MPI_Recv(&msgRcv.elemNr, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &status);
        msgRcv.elem = malloc(sizeof(char) * msgRcv.elemNr);
        MPI_Recv(msgRcv.elem, msgRcv.elemNr, MPI_CHAR, 1, 1, MPI_COMM_WORLD, &status);
        printf("I received\n");

        for (i = 0; i < msgRcv.elemNr; i++)
            printf("element %d: %c\n", i, msgRcv.elem[i]);
    
        free(msgRcv.elem);        
    }

    if (rank != 0 && msgSnd.elem != NULL)
        free(msgSnd.elem);

    MPI_Finalize();

    return 0;
}

方法2:使用MPI打包/解包(适合复杂结构)

如果结构体包含多个动态字段,可以用MPI_Pack把所有数据打包到缓冲区,再发送整个缓冲区;接收端用MPI_Unpack解析:

示例代码片段:

// 发送端打包
int pack_size = 0;
// 先计算需要的缓冲区大小
MPI_Pack_size(1, MPI_INT, MPI_COMM_WORLD, &pack_size);
MPI_Pack_size(n, MPI_CHAR, MPI_COMM_WORLD, &pack_size);
char *buf = malloc(pack_size);
int position = 0;
MPI_Pack(&msgSnd.elemNr, 1, MPI_INT, buf, pack_size, &position, MPI_COMM_WORLD);
MPI_Pack(msgSnd.elem, n, MPI_CHAR, buf, pack_size, &position, MPI_COMM_WORLD);
// 发送缓冲区
MPI_Send(buf, pack_size, MPI_PACKED, 0, 0, MPI_COMM_WORLD);

// 接收端解包
char *rcv_buf = malloc(pack_size);
MPI_Recv(rcv_buf, pack_size, MPI_PACKED, 1, 0, MPI_COMM_WORLD, &status);
position = 0;
MPI_Unpack(rcv_buf, pack_size, &position, &msgRcv.elemNr, 1, MPI_INT, MPI_COMM_WORLD);
msgRcv.elem = malloc(msgRcv.elemNr);
MPI_Unpack(rcv_buf, pack_size, &position, msgRcv.elem, msgRcv.elemNr, MPI_CHAR, MPI_COMM_WORLD);

注意事项

  • 永远不要直接发送指针:MPI是跨进程通信,不同进程的地址空间独立,指针值在其他进程中无效。
  • 如果要使用MPI自定义派生类型,结构体中的数组必须是静态数组(比如char elem[5]),不能是动态分配的指针。

内容的提问来源于stack exchange,提问作者user7041535

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 02:41:58