C语言链表队列入队后所有节点数据被最新请求覆盖的问题
这个问题是典型的栈地址逃逸到函数作用域外导致的指针bug,叠加几处低级笔误共同引发的异常:
核心触发逻辑
你在runCommand函数中定义的request r是栈上的局部自动变量,函数执行结束后这块栈内存就会被回收,后续的函数调用会直接覆盖这块内存的存储内容。
你在代码中直接把这个局部变量的地址&r传入addToQueue入队,本质上队列中所有节点保存的都是同一个栈内存地址的指针——并没有为每个请求分配独立的存储空间。
由于C语言函数栈帧的内存布局是固定的,每次调用runCommand时,局部变量r永远会被分配在相同的栈地址上。因此每次新请求的内容写入r时,队列中所有指向这个地址的“节点”都会同步读到最新的请求值,最终出现所有队列节点数据完全同步更新的诡异现象。
连带的错误点
- 调用
addToQueue时第二个参数直接写了queue,没有使用形参传入的que指针,属于变量名笔误,仅因重名没有直接触发崩溃。 createQueue函数最后写的是return queue;,和函数内定义的局部堆指针que变量名不一致,返回的指针完全是野值。- 入队逻辑没有把新节点的
next指针初始化为NULL,反而给局部变量的next随意malloc了一块无用内存,造成持续内存泄漏。 - 首次入队后把队列的
tail指针设为NULL的逻辑不符合队列规范,导致后续入队时的tail判断分支完全走偏。
为每个请求分配独立的堆内存
调整runCommand的逻辑:先在栈上把请求的各类字段填充完成,再通过malloc在堆上分配一块全新的request结构体,将栈上临时变量的内容完整拷贝到堆内存中,把这个堆指针传入addToQueue入队,彻底避免存储栈地址的问题。
核心代码逻辑参考:// 栈上临时变量用于填充请求字段 request tmp_r; // 所有原来对r的赋值逻辑全部替换成对tmp_r的操作 tmp_r.request_id = reqID; tmp_r.valid = 1; tmp_r.check_acc_id = 0; tmp_r.transfers = (transfer*)malloc(sizeof(transfer) * 10); tmp_r.num_transfers = 0; tmp_r.starttime = time; // 为每个请求分配独立的堆内存 request *node = malloc(sizeof(request)); // 完整拷贝字段,替代直接传栈地址的操作 memcpy(node, &tmp_r, sizeof(request)); node->next = NULL; // 必须初始化新节点的next为NULL,避免野指针原来代码中冗余的
r.next = (request*)malloc(sizeof(request));逻辑可以直接删掉,新节点的next不需要额外分配内存,直接设为NULL即可,同时记得释放这块冗余malloc的内存,避免泄漏。修正所有低级笔误
- 把
createQueue函数的返回值从return queue;改为return que;,返回正确分配的堆队列指针。 - 所有调用
addToQueue的位置,第二个参数必须传入形参que,不要漏写、不要错用变量名。
- 把
修正队列入队逻辑
调整为标准的链表队列处理逻辑,解决tail指针初始化错误的问题:if (que->num_jobs == 0) { // 空队列时,head和tail都要指向新节点,不要把tail设为NULL que->head = req; que->tail = req; } else { // 非空队列时,将当前tail节点的next指向新节点,再移动tail指针 que->tail->next = req; que->tail = req; } que->tail->next = NULL; // 保证尾节点的next始终为NULL,避免链表成环补充内存管理逻辑
后续实现出队操作时,记得释放每个堆上分配的request节点、以及节点内malloc的transfers数组,避免内存泄漏。
内容的提问来源于stack exchange,提问作者rradtke30

