SharedQueueEx逻辑与原类一致但程序退出时堆内存损坏问题求助
有两个逻辑完全一致的类:SharedQueue和SharedQueueEx。二者都包含成员m_pTail、m_pHead,Recv和Send函数通过操作这两个成员访问内部共享队列。本文只讨论Recv函数,暂时忽略Send函数。
进入Recv/Send函数时,原逻辑总会检查m_pTail/m_pHead是否为空,但实际上这两个指针一旦被初始化为非NULL值,整个程序生命周期内都不会再变为NULL。
SharedQueue(int nMaxQueueLength = MAX_QUEUE_LENGTH, int nNodeLength = MAX_NODE_LENGTH, unsigned int nDelay = 0) { m_pHead = m_pTail = nullptr; ... } int Recv(const char* pBuffer, int nLength) { int nRecvLength = 0; while (nRecvLength < nLength) { QNode* pQE = nullptr; #pragma region pQE = Enqueue(); ::EnterCriticalSection(&m_cs); if (!m_pTail) // 初始化链表首个节点 pQE = m_pHead = m_pTail = new QNode(0, m_nNodeLength); // 检查最后一个接收节点是否已写满 else if (m_pTail->m_nRecvOffset >= m_nNodeLength) ... }
因此对SharedQueue做了小幅修改,在构造函数中就初始化m_pTail,省去Recv中对m_pTail的空校验,修改后的类命名为SharedQueueEx。
SharedQueueEx(int nMaxQueueLength = MAX_QUEUE_LENGTH, int nNodeLength = MAX_NODE_LENGTH, unsigned int nDelay = 0) { m_pHead = m_pTail = nullptr; // 预分配链表首个节点 m_pTail = m_pFirstNode = new QNode(0, m_nNodeLength); ... } int Recv(const char* pBuffer, int nLength) { int nRecvLength = 0; while (nRecvLength < nLength) { QNode* pQE = nullptr; #pragma region pQE = Enqueue(); ::EnterCriticalSection(&m_cs); // 检查最后一个接收节点是否已写满 if (m_pTail->m_nRecvOffset >= m_nNodeLength) { // 检查队列是否达到最大长度 int nNewSequence = m_pTail->m_nSequence + 1; if (nNewSequence >= m_nMaxQueueLength) { if (!m_pHead) m_pHead = m_pFirstNode; ... }
SharedQueue和SharedQueueEx逻辑完全一致,同时创建两个类的实例,传入相同参数调用Recv,调试确认每次Recv调用二者运行表现完全相同。
但使用SharedQueueEx的测试程序退出时会出现内存损坏,花费大量时间排查原因未果。
测试复现说明
- 可运行的VC2017测试解决方案下载地址:SharedQueueTest.zip
SharedQueueTest.cpp第16行代码如下:
#define TEST_SQEX
- 注释掉上述宏运行程序,运行正常;定义该宏运行,程序退出时会崩溃,错误提示如下:

备注
若想了解SharedQueue的实现原理,可参考以下链接:SharedQueue工作原理
附件中的项目源码已做如下修改并重新上传:
int CFeedClient::Send(const char* lpcs, int nLength) { UINT r1 = random(0, nLength); UINT r2 = random(0, nLength); UINT nSent = (r1 > r2) ? (r1 - r2 + 1) : (r1 < r2) ? (r2 - r1 + 1) : 0; return nSent; } unsigned __stdcall RecvThreadFunc(LPVOID pParam) { int nLength; char szbuf[] = "`~!@#$%^&*()_+-=[]{}\\|,./<>?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; ATLASSERT(strlen(szbuf) == _countof(szbuf) - 1); // 不统计末尾的NULL字符 while (bContRecv) { // 从其他源接收数据 //... // (填充szBuf和nLength),可对数据进行处理或转换 // 将接收的数据流存入队列 UINT r1 = random(0, _countof(szbuf) - 2); // 不统计末尾的NULL字符 UINT r2 = random(0, _countof(szbuf) - 2); // 不统计末尾的NULL字符 if (r1 > r2) { nLength = r1 - r2 + 1; #if defined(TEST_SQEX) sqEx.Recv(szbuf + r2, nLength ); #else // TEST_SQEX sq .Recv(szbuf + r2, nLength ); #endif // TEST_SQEX } else if (r1 < r2) { nLength = r2 - r1 + 1; #if defined(TEST_SQEX) sqEx.Recv(szbuf + r1, nLength ); #else // TEST_SQEX sq .Recv(szbuf + r1, nLength ); #endif // TEST_SQEX } //else if (r1 == r2) // continue; } return 0; }
复现堆损坏的最简方案:按如下代码替换main()和~CSharedQueueEx(),无需创建额外线程,仅在主线程调用一次Recv即可复现:
int main() { std::cout << "Hello World!\n"; char szbuf[] = "`~!@#$%^&*()_+-=[]{}\\|,./<>?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; ATLASSERT(strlen(szbuf) == _countof(szbuf) - 1); // 不统计末尾的NULL字符 int nLength = 0; // 从其他源接收数据 //... // (填充szBuf和nLength),可对数据进行处理或转换 // 将接收的数据流存入队列 UINT r1 = random(0, _countof(szbuf) - 2); // 不统计末尾的NULL字符 UINT r2 = random(0, _countof(szbuf) - 2); // 不统计末尾的NULL字符 if (r1 > r2) { nLength = r1 - r2 + 1; #if defined(TEST_SQEX) sqEx.Recv(szbuf + r2, nLength ); #else // TEST_SQEX sq .Recv(szbuf + r2, nLength ); #endif // TEST_SQEX } else if (r1 < r2) { nLength = r2 - r1 + 1; #if defined(TEST_SQEX) sqEx.Recv(szbuf + r1, nLength ); #else // TEST_SQEX sq .Recv(szbuf + r1, nLength ); #endif // TEST_SQEX } return 0; } ~SharedQueueEx() { ::EnterCriticalSection(&m_cs); // 清空所有节点 try { #if defined(TEST_SQEX) if (!m_pHead) m_pHead = m_pFirstNode; #endif // TEST_SQEX for (QNode* pNext; m_pHead; m_pHead = pNext) pNext = m_pHead->m_pNext, delete m_pHead; m_pHead = m_pTail = nullptr; } catch (...) { } ::LeaveCriticalSection(&m_cs); ::DeleteCriticalSection(&m_cs); }
程序退出时会弹出“Heap Corruption...”提示框。
内容的提问来源于stack exchange,提问作者HaeRim Lee
相关产品推荐
相关产品推荐

