_04-堆的插入和删除

该博客介绍了小顶堆的初始化、插入、获取堆头元素、弹出堆顶及内容打印的实现过程。堆是一种特殊的树形数据结构,常用于优先队列。在这个实例中,0号位置用作守卫,不存储有效数据,所有元素从1号位置开始。源码展示了如何在C++中创建和操作小顶堆,包括插入元素时的下沉操作以及弹出最小元素时的调整过程。

题意:

本题要求完成堆的初始化、插入、堆头元素的获取、堆的弹出和堆内容的打印。

本题的堆是小顶堆,也就是每次弹出的都是最小的那个元素。同时0号位是一个值为-1的守卫,0号位不使用,所有元素从1号位开始放置

思路:

①创建堆。包括其数值、当前容量和最大容量。

②堆的插入。若插入值比最后一个节点的下一个节点i的父节点i/2的值小,则将父节点值赋给i(第一次这个i的节点是空的,第二次这个节点已经赋值给其儿子节点),i/=2。循环至插入值比i/2的值大,则将插入值赋给i。

③堆的弹出。因为堆的数值类型定义为int*,所以直接返回数组下标为1的值即可。

源码:

//struct HEAP{
//    int* data;
//    int reserved_size;
//    int cur_size;
//    HEAP(int size);
//    void push( int value );
//    int top() const;
//    void pop();
//    void show_content() const;
//};

HEAP::HEAP(int size) {
    reserved_size = size;
    data = new int[size];
    cur_size = 0;
    data[0] = -1;
}

void HEAP::push(int value) {
    int i = 0;
    i = ++cur_size;
    for (; data[i / 2] > value; i /= 2)
        data[i] = data[i / 2];
    data[i] = value;
}

int HEAP::top() const {
    return data[1];
}

void HEAP::pop() {
    int parent = 0, child = 0, t = 0;
    t = data[cur_size--];
    for (parent = 1; parent * 2 <= cur_size; parent = child) {
        child = parent * 2;
        if ((child != cur_size) && data[child] > data[child + 1])
            child++;
        if (t <= data[child])break;
        else
            data[parent] = data[child];
    }
    data[parent] = t;
}

void HEAP::show_content() const {
    for (int i = 0; i <= cur_size; i++)
        cout << data[i] << " ";
    cout << endl;
}

ps:无

<think>好的,我现在需要仔细分析用户提供的MQTT_AddToRetransmitList函数代码,并理解其功能潜在问题。首先,我会逐行阅读代码,了解每个部分的作用。 函数开始时使用malloc分配了一个RetransmitList结构体的内存。这里可能存在内存分配失败的风险,因为malloc可能返回NULL,但代码中没有检查这一点,这可能导致后续操作引发空指针异常。 接下来,代码初始化新节点的各个成员变量,包括packet_id、qos_level、send_time(使用HAL_GetTick获取当前时间戳)、retry_count设为0,然后为packet_data分配内存,并拷贝数据。同样,这里再次使用malloc分配packet_data的内存,但没有检查是否成功,memcpy可能会因此导致问题。 然后,将新节点插入到链表头部,更新mqtt_retransmit_list指针。这部分的链表操作是正确的,但需要注意多线程环境下的竞争条件,因为该函数可能不是线程安全的。 用户之前询问过内存池的实现,而当前代码使用的是传统的malloc动态分配。对比之前的例子,用户可能关心如何将此处改为内存池以提高实时性可靠性。例如,使用预分配的内存池来替代malloc,可以避免动态内存分配的不确定性碎片问题。 此外,代码中可能存在内存泄漏的风险。例如,当函数多次调用时,每次都会分配新的节点packet_data,但在释放时如果没有正确管理,可能导致内存未释放。特别是如果后续的重传机制没有正确释放这些节点,就会积累内存泄漏。 另一个考虑点是资源耗尽的情况。如果系统频繁调用此函数,而重传列表没有及时清理,链表会不断增长,消耗大量内存,尤其是在资源受限的嵌入式系统中,这可能导致系统崩溃。 需要检查是否有相应的释放机制,比如在收到ACK后是否有函数从链表中移除节点并释放内存。当前代码只展示了添加节点到链表,没有展示删除节点的部分,这可能是一个隐患。 此外,使用HAL_GetTick()来获取时间戳,需要考虑该函数的时间单位溢出问题。例如,如果使用的是32位的tick计数,大约49天后会溢出,这可能在长期运行的系统中引发问题,需要处理时间比较时的溢出情况。 关于回调函数cb,代码中将其存储在节点中,但需要确保在回调执行时,相关数据仍然有效。例如,如果节点在回调触发前被释放,可能导致回调访问无效内存。 总结下来,这段代码的主要问题包括: 1. 未检查malloc返回值,存在潜在的空指针解引用风险。 2. 动态内存分配可能导致的实时性问题内存碎片。 3. 缺乏内存释放机制,可能导致内存泄漏。 4. 线程安全性问题,如果多个线程同时操作链表可能导致数据损坏。 5. Tick溢出问题回调函数的安全调用。 针对这些问题,可能的改进措施包括: - 使用内存池替代malloc,如之前讨论的静态内存池,提高分配速度确定性。 - 添加错误检查,确保内存分配成功后再进行操作。 - 实现节点释放函数,并在适当的时候(如收到ACK或超时后)调用以释放资源。 - 添加线程安全机制,如互斥锁,保护链表的操作。 - 处理tick溢出问题,使用适当的时间比较方法。 - 确保回调函数在安全的环境下执行,避免访问已释放的内存。 最后,需要结合用户之前关于内存池的兴趣,建议如何将当前代码改为使用内存池,提升实时性可靠性,同时指出当前实现的局限性,并提供优化建议。</think>--- ### 代码功能解析 这是一个MQTT协议重传队列的**消息添加函数**,核心逻辑是为需要可靠传输的消息创建重传控制节点,并插入链表头部。主要工作流程如下: ```c void MQTT_AddToRetransmitList(uint8_t qos, uint16_t pid, void* data, size_t len, void (cb)(uint16_t)) { // 创建重传控制节点(动态内存申请) RetransmitList* new_node = (RetransmitList*)malloc(sizeof(RetransmitList)); // 初始化节点数据 new_node->node.packet_id = pid; // 报文ID new_node->node.qos_level = qos; // QoS等级 new_node->node.send_time = HAL_GetTick();// 记录发送时间戳 new_node->node.retry_count = 0; // 初始化重试计数器 // 深拷贝报文内容 new_node->node.packet_data = malloc(len);// 二次动态内存申请 memcpy(new_node->node.packet_data, data, len); new_node->node.packet_len = len; // 注册ACK回调函数 new_node->node.ack_callback = cb; // 链表头插法 new_node->next = mqtt_retransmit_list; mqtt_retransmit_list = new_node; } ``` --- ### 关键设计分析 #### ✅ 重传机制核心要素 1. **时间戳记录**:通过`HAL_GetTick()`记录发送时间,为后续超时重传提供判断依据 2. **重试计数器**:`retry_count`用于限制最大重传次数(示例中未体现递增逻辑) 3. **数据完整性**:使用`memcpy`深拷贝保证原始数据可被释放 4. **QoS支持**:通过`qos_level`实现不同服务质量等级的消息处理 #### ✅ 链表结构优势 - **头插法效率**:$O(1)$时间复杂度快速插入,适合高频更新 - **遍历查找方便**:通过`next`指针可遍历检查超时消息 --- ### 潜在风险预警 #### ❗ 内存管理问题 1. **双malloc未校验**:连续两次`malloc`均未检查返回值(嵌入式系统重大隐患) 2. **内存泄漏风险**:未看到对应的`FreeNode`实现,容易积累未释放节点 3. **内存碎片**:频繁申请释放会导致内存碎片化 #### ❗ 实时性缺陷 - **动态分配耗时**:`malloc`在资源受限系统中可能引发不可预测的延迟 --- ### 改进方案建议 #### 方案1:内存池改造(接续前文优化) ```c // 使用内存池替代malloc(需提前定义POOL_SIZE) RetransmitList* new_node = GetRetransNode(); // 从池中获取节点 if(!new_node) { // 处理池耗尽的情况(如丢弃最旧报文) return; } // 使用独立数据池存储packet_data new_node->node.packet_data = GetDataBuffer(len); ``` #### 方案2:防御性编程加强 ```c // 增加空指针检查 if(!new_node || !new_node->node.packet_data) { LOG_ERROR("Memory allocation failed"); if(new_node) free(new_node); // 防止部分分配 return; } // 设置最大重试次数 #define MAX_RETRY 3 new_node->node.retry_count = MAX_RETRY; // 倒计时方式更安全 ``` #### 方案3:超时管理优化 ```c // 建议在链表中按时间排序插入 // 可加速超时检测时的遍历效率 ``` --- ### 典型工作流程示意 ```mermaid sequenceDiagram participant App as 应用层 participant MQTT as MQTT协议栈 participant Net as 网络层 App->>MQTT: 发布QoS1消息 MQTT->>MQTT: AddToRetransmitList() MQTT->>Net: 发送报文 loop 超时检测 MQTT->>MQTT: 检查send_time alt 超时未确认 MQTT->>Net: 重发报文 MQTT->>MQTT: retry_count-- end end Net-->>MQTT: 收到ACK MQTT->>MQTT: 从链表移除节点 MQTT->>App: 触发ack_callback ``` --- ### 工程实践要点 1. **心跳检测**:建议单独设置重传队列扫描周期(如每秒检查一次) 2. **线程安全**:若在RTOS中使用,需添加互斥锁保护链表操作 3. **存活时间**:建议设置全局最大存活时间(如30秒),防止僵尸节点 4. **监控统计**:添加节点计数监控,预防内存耗尽 实际项目中可参考Linux内核的`sk_buff`设计,实现高效的内存管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别搜了,自己做

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值