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

释放含模板结构体指针的队列节点时触发堆损坏错误求助

堆损坏错误排查:C++模板队列实现问题

我第一次用C++模板实现一个能存储任意类的简易Queue数据结构,写了TestClass测试,但释放队列节点时总是触发堆损坏错误。怀疑是TestClass大小远大于QueueNode导致的,但不确定具体原因。试过释放节点本身、置空data等操作都无法解决问题。


相关代码

QueueTest.h

#include "../../src/Core/Logging/Logging.h"
#include <iostream>
#include <string>
class TestClass {
    public:
        int num;
        std::string str;
        TestClass(int inNum, std::string inStr);
};


int TestQueueMain(Logger Logs);
int TestEdgeCase1();

QueueTest.cpp

#include "QueueTest.h"
#include "../../src/Core/DataStructures/Queue.h"

TestClass::TestClass(int inNum, std::string inStr) : num(inNum), str(inStr) {}
std::ostream& operator<< (std::ostream &out, TestClass const& data) {
    out << "Num: " << data.num << " Str: " << data.str;
    return out;
}

int TestQueueMain(Logger Logs) {
    Logs.createLog(Info, "Queue tests started", std::source_location::current());
    int failures = 0;
    failures += TestEdgeCase1();

    Logs.createLog(Info, "Queue tests finished with " + std::to_string(failures) + " error(s)", std::source_location::current());
    return failures;
}

int TestEdgeCase1() {
    Queue q = Queue<TestClass>();
    TestClass t1 = TestClass(1, "a");
    std::cout << sizeof(t1) << std::endl; // 输出48
    q.Enqueue(&t1);
    std::cout << "test" << std::endl;
    TestClass temp = q.Dequeue(); // 错误发生在这里
    std::cout << temp;
    
    return 0;
}

Queue.h

#include <iostream>

template <class T>
struct QueueNode {
    T* data; // 存储任意类的指针
    struct QueueNode* next;
};

template <class T>
class Queue {
    public:
        Queue() : Start(nullptr), End(nullptr), Size(0) {}
        ~Queue() { Clean(); }

        template<class T>
        inline void Enqueue(T* Obj) {
            QueueNode<T>* node = (QueueNode<T>*)malloc(sizeof(QueueNode<T>*));
            std::cout << "Inserting obj: " << *Obj << std::endl;
            node->data = Obj;
            node->next = nullptr;
            if (Start == nullptr) {
                Start = node;
                End = node;
            } else {
                End->next = node;
                End = node;
            }
        }

        inline T Dequeue() {
            if (Start == nullptr) return *Start->data; // 返回空指针(逻辑错误)
            else if (Start == End) {
                std::cout << "START SIZE: " << sizeof(Start) << std::endl; // 输出8
                std::cout << "START SIZE2: " << sizeof(Start->data) << std::endl; //输出8
                T val = *Start->data;
                End = nullptr;
                Start->data = nullptr;
                free(Start);
                return val;
            }
            QueueNode<T>* temp = Start;
            T val = *Start->data;
            Start = Start->next;
            free(temp);
            return val;
        }

        inline void Clean() {
            struct QueueNode<T>* temp;
            while (Start != nullptr) {
                temp = Start;
                Start = Start->next;
                free(temp);
            }
            free(Start);
            std::cout << "Cleaning queue" << std::endl;
        }
    private:
        QueueNode<T>* Start;
        QueueNode<T>* End;
        int Size;
};

问题根源与修复方案

1. 内存分配大小错误(核心问题)

在Enqueue方法中,你用malloc(sizeof(QueueNode<T>*))分配内存,这只分配了一个指针的大小(64位系统为8字节),但QueueNode<T>包含两个指针成员(T*和QueueNode<T>*),实际需要16字节内存。分配的内存不足,写入node->next时会直接越界,触发堆损坏。

修复:将内存分配的大小改为sizeof(QueueNode<T>):

QueueNode<T>* node = (QueueNode<T>*)malloc(sizeof(QueueNode<T>));

2. C++中malloc/free的不规范使用

虽然QueueNode是POD结构,用malloc没问题,但C++中更推荐用new/delete管理对象,避免后续扩展非POD类型时出问题:

// Enqueue替换malloc
QueueNode<T>* node = new QueueNode<T>();
node->data = Obj;
node->next = nullptr;

// Dequeue和Clean中替换free
delete temp;

3. 空队列访问崩溃问题

当队列为空时,Dequeue中直接返回*Start->data会访问空指针的成员,导致崩溃。需要先判断空队列:

inline T Dequeue() {
    if (Start == nullptr) {
        // 可选:抛出异常或返回默认构造对象
        throw std::runtime_error("Queue is empty");
        // 或 return T();
    }
    // 后续逻辑...
}

4. Clean方法重复释放问题

循环结束后Start已经是nullptr,再调用free(Start)属于冗余操作(虽然free(nullptr)安全,但没必要),直接删除该行:

inline void Clean() {
    struct QueueNode<T>* temp;
    while (Start != nullptr) {
        temp = Start;
        Start = Start->next;
        free(temp); // 或delete temp
    }
    // 删除free(Start);
    std::cout << "Cleaning queue" << std::endl;
}

5. Enqueue模板参数重复定义

Enqueue方法中的template<class T>是多余的,会隐藏外层模板类的T参数,直接删除即可:

// 去掉template<class T>
inline void Enqueue(T* Obj) {
    // 逻辑不变
}

6. 临时对象生命周期风险

测试代码中传入栈上对象t1的指针,虽然当前测试没问题,但如果t1先于队列销毁,队列中的指针会变成悬空指针。如果要存储动态对象,应使用new TestClass(1, "a"),并在释放节点时delete掉data避免内存泄漏。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 02:47:09