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
相关产品推荐
相关产品推荐

