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

为何msgsnd()与msgrcv()拆分到不同函数后运行异常?

分析与解决:System V消息队列抽离函数后的异常问题

首先,你的问题核心是不符合System V消息队列的规范要求,导致栈溢出和未定义行为,而不同函数抽离方式下的栈布局差异让异常表现不一致。下面详细拆解原因和解决方案:

问题根源

1. 消息结构体不符合System V队列规范

System V消息队列要求消息必须以long mtype作为结构体的第一个成员——这个字段用于标识消息类型,是msgsnd和msgrcv正确工作的基础。你的Msg_context结构体没有这个字段,导致:

  • msgsnd会错误地把nodeId(int类型)当作mtype读取,然后读取栈上的垃圾数据作为消息内容。
  • msgrcv接收时,又会把垃圾数据写入msgRecv.nodeId,只是在正常场景下,栈布局巧合让你得到了正确结果。

2. 接收时长度参数过大导致栈溢出

你在msgrcv中传入的第三个参数是BUFSIZ(通常是8192字节),但Msg_context只有4字节。这会让msgrcv尝试向栈上的msgRecv写入远超其容量的数据,造成栈溢出:

  • 正常场景下,msgRecv在test2的栈帧中,后面的栈空间暂时未被使用,溢出的垃圾数据没有立刻破坏关键信息。
  • 抽离msgrcv到单独函数后,com_process_recv的栈帧更小,溢出直接覆盖了函数返回地址或其他关键数据,导致消息接收异常、输出错误。

3. 发送与接收的长度不匹配

发送时你指定长度为4(sizeof(int)),但接收时用BUFSIZ,进一步加剧了栈溢出的影响。

正确解决方案

步骤1:修正消息结构体格式

按照规范定义包含mtype的消息结构体:

typedef struct Msg_context {
    long mtype;  // 必须是第一个成员,用于标识消息类型
    int nodeId;  // 实际传递的消息数据
} Msg_context;

步骤2:修正msgsnd的调用参数

发送时,msgsz参数是消息数据部分的长度(不包括mtype),同时设置mtype为你需要的标识(比如固定值1,或目标节点ID):

int com_process_send(int nodeId) {
    int i = 1;
    for (i = 1; i <= 3; i++) {
        if (i == nodeId) {
            continue;
        }
        int msgid = msgget((key_t)i, 0666 | IPC_CREAT);
        if (msgid == -1) {
            perror("msgget failed in send");
            continue;
        }
        Msg_context msgSend;
        msgSend.mtype = 1;  // 用固定类型标识普通消息
        msgSend.nodeId = nodeId;
        // 消息数据长度是sizeof(msgSend.nodeId),不包含mtype
        int ret = msgsnd(msgid, &msgSend, sizeof(msgSend.nodeId), 0);
        if (ret == -1) {
            perror("msgsnd failed");
        }
        printf("[send] nodeId = %d, dest queue = %d\n", nodeId, i);
    }
    return 0;
}

步骤3:修正msgrcv的调用参数

接收时,同样指定数据部分的长度,并且可以通过mtype参数过滤接收特定类型的消息:

int com_process_recv(int nodeId) {
    int msgrecvId = msgget((key_t)nodeId, 0666 | IPC_CREAT);
    if (msgrecvId == -1) {
        perror("msgget failed in recv");
        return -1;
    }
    Msg_context msgRecv;
    // 接收mtype=1的消息,数据长度匹配实际内容
    ssize_t ret = msgrcv(msgrecvId, &msgRecv, sizeof(msgRecv.nodeId), 1, 0);
    if (ret == -1) {
        perror("msgrcv failed");
        return -1;
    }
    printf("[recv] nodeId = %d, recv.nodeId = %d\n", nodeId, msgRecv.nodeId);
    return 0;
}

步骤4:添加消息队列清理(可选但推荐)

程序退出时,用msgctl删除创建的消息队列,避免系统资源泄漏:

void cleanup_msg_queue(int nodeId) {
    int msgid = msgget((key_t)nodeId, 0);
    if (msgid != -1) {
        msgctl(msgid, IPC_RMID, NULL);
    }
}

可以在进程退出前(比如捕获SIGINT信号)调用这个函数。

为什么不同场景表现不同?

  • 正常场景:msgsnd和msgrcv在同一个函数栈中,栈布局巧合让溢出的垃圾数据刚好是之前发送的nodeId,掩盖了格式错误。
  • 异常场景:抽离msgrcv后,函数栈帧更小,栈溢出直接破坏了关键数据,导致异常。
  • 恢复场景:同时抽离两个函数后,com_process_send中的printf改变了栈布局,暂时掩盖了溢出问题,但这只是巧合,并非真正修复。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:05:03