C++从UDP缓冲区读取结构体中string时出现free()无效指针错误如何解决
核心问题根因
你确实误用了memcpy,且发送端的序列化发送逻辑从根上就存在错误:std::string属于非POD类型,内部仅存储指向堆内存的指针、长度、容量等控制字段,实际字符串内容存在堆上,直接对std::string对象、或包含std::string的结构体做内存拷贝、网络发送,本质上传输的是无意义的野指针,接收端直接覆盖std::string的内存结构后,析构时就会触发free(): invalid pointer崩溃。
1. 发送端错误点&修正
错误点
- 给
hdr.dataSize赋值时用了sizeof(send1.data):这个值是std::string对象本身的固定大小(一般为8/16/24字节,和平台相关),和你实际序列化后的内容长度毫无关系,应该替换为send1.data.size() - 直接对包含
std::string的SerialMessage结构体强转后发送:结构体内存布局里std::string部分仅存堆指针,你发出去的不是实际序列化数据,接收端拿到完全无效 - 未考虑结构体内存对齐:
Header结构体如果不做对齐约束,不同编译器的对齐规则可能导致头部长度和接收端预期不一致,出现偏移错误
修正后的发送逻辑
// 序列化逻辑保持不变 std::ostringstream outStream; { cereal::BinaryOutputArchive archive_out(outStream); archive_out(data1); } SerialMessage send1; send1.hdr.domainId = 6; send1.hdr.msgId = 1; send1.data = outStream.str(); send1.hdr.dataSize = send1.data.size(); // 这里改,用实际内容长度 send1.hdr.timeStamp = getTimeStamp(); send1.ftr.temp = 0x1234; // 按需给Footer赋值 // 手动拼接连续的发送缓冲区,不要直接发SerialMessage结构体 size_t totalSendSize = sizeof(Header) + send1.data.size() + sizeof(Footer); uint8_t* sendBuf = new uint8_t[totalSendSize]; memcpy(sendBuf, &send1.hdr, sizeof(Header)); // 拷头部 memcpy(sendBuf + sizeof(Header), send1.data.data(), send1.data.size()); // 拷实际序列化内容 memcpy(sendBuf + sizeof(Header) + send1.data.size(), &send1.ftr, sizeof(Footer)); // 拷尾部 int nbytes = udp->send(sendBuf, totalSendSize); delete[] sendBuf; // 用完释放缓冲区
补充:建议给
Header结构体加对齐约束,避免偏移错误:struct __attribute__((packed)) Header // GCC/Clang 下用这个,MSVC用#pragma pack(1) { uint8_t domainId; uint8_t msgId; int msgCnt; uint16_t dataSize; uint64_t timeStamp; };
2. 接收端错误点&修正
错误点
memcpy(&serData, buffer, sizeof(inHdr.dataSize))逻辑完全错误:你直接往std::string对象的内存地址写数据,覆盖了它内部的指针、长度等控制字段,造出了完全非法的std::string对象,析构时必然崩溃- 未做偏移:实际序列化内容在
buffer + sizeof(Header)的位置,不是从buffer开头取 - 未做长度校验:收到的包长度可能小于预期,直接读取会出现越界访问
修正后的接收逻辑
else{ std::cout << "\n\tReceived Message, size: " << recvSize << '\n'; Header inHdr; memcpy(&inHdr, buffer, sizeof(Header)); // 先校验包长度是否完整,避免越界 const size_t expectTotalSize = sizeof(Header) + inHdr.dataSize + sizeof(Footer); if (recvSize < expectTotalSize) { std::cout << "\tincomplete packet, skip\n"; continue; } // 直接用string的长度构造函数取数据,不需要memcpy std::string serData(reinterpret_cast<char*>(buffer + sizeof(Header)), inHdr.dataSize); // 按需读取Footer Footer inFtr; memcpy(&inFtr, buffer + sizeof(Header) + inHdr.dataSize, sizeof(Footer)); std::cout << "\tdID: " << (int)inHdr.domainId << "\n\tmID: " << (int)inHdr.msgId << "\n\tdataLength: " << inHdr.dataSize << "\n\ttimeStamp: " << inHdr.timeStamp << std::endl; std::cout << "\nData:\n\t" << serData << std::endl; // 直接反序列化即可 std::istringstream inStream(serData); SerialData data; { cereal::BinaryInputArchive archive_in(inStream); archive_in(data); } printSerializedMessageData(data); }
内容的提问来源于stack exchange,提问作者Chip Brommer
相关产品推荐
相关产品推荐

