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

C语言初始化结构体为何导致其他局部变量被异常置空?

C语言链表实现节点值异常覆盖问题

问题复现代码

#include <stdio.h>

struct Node {
    int has_value;
    int value;
    struct Node* next;
};

typedef struct Node Node;

void push(Node* node, int value) {
    while(1) {
        if (!node->has_value) {
            node->value = value;
            node->has_value = 1;
            return;
        }
        if (node->next) {
            node = node->next;
            continue;
        }
        else {
            Node new = {};
            new.value = value;
            new.has_value = 1;
            node->next = &new;
            return;
        }
    }
}

int main(void) {
    Node node = {};

    push(&node, 10);
    push(&node, 20);
    push(&node, 30);

    Node second = *node.next;
    Node third = *second.next;

    printf("%i\n", node.value);
    printf("%i\n", second.value);
    printf("%i\n", third.value);
}

异常现象

  • 预期输出:node、second、third取值分别为10、20、30
  • 实际输出:second和third完全一致,值均为30且next指针相同
  • gdb调试特征:单步进入push(&node, 30)调用,执行到else分支的Node new = {};语句时,原本指向值为20的第二个节点的node指针,指向的内存会被突然置为{.has_value = 0, value = 0, next = 0x0}
  • 对照测试:若不在push函数内创建第三个节点,改为在main函数手动初始化新节点并挂载到链表,程序运行符合预期
  • 编译命令:gcc main.c

根本原因

代码存在栈局部变量地址逃逸的未定义行为:

  1. push函数中Node new = {};定义的是栈上分配的局部变量,该变量的内存生命周期仅在当前else分支块内,push函数返回后,这块栈内存就会被编译器回收,后续函数调用会直接复用这块内存存储新的栈数据,此时指向这块内存的指针属于悬空指针。
  2. 每次调用push函数时,栈帧的内存布局是固定的,局部变量new的栈地址完全一致。第一次调用push存入20时,new的地址被赋值给头节点的next指针;第二次调用push存入30时,新的new变量刚好复用了之前存20的栈地址,初始化new的操作直接覆盖了原20节点的内存,这就是gdb观察到节点值被莫名置空的原因。
  3. 最终链表中所有通过push新增节点的next指针,实际都指向同一个栈内存地址,该地址存储的是最后一次push写入的值30,因此会出现second和third值完全相同的现象。
  4. 在main函数内手动初始化节点可以正常运行,是因为main的栈帧生命周期贯穿整个程序运行期,其局部变量的内存不会被中途回收覆盖。

修复方案

新增节点不能使用栈上局部变量,需要改用malloc在堆上分配内存,堆内存的生命周期由开发者手动控制,不会随函数返回被回收:

  1. 新增头文件引用<stdlib.h>以使用内存分配函数
  2. 修改push函数else分支的节点创建逻辑,初始化新节点时必须将next指针置空避免野指针
  3. 程序退出前需要遍历链表逐个调用free释放堆内存,避免内存泄漏

修复后的核心代码:

#include <stdlib.h> // 新增头文件

// ... 其余结构体定义、typedef保持不变 ...

void push(Node* node, int value) {
    while(1) {
        if (!node->has_value) {
            node->value = value;
            node->has_value = 1;
            node->next = NULL; // 头节点初始化补全next置空
            return;
        }
        if (node->next) {
            node = node->next;
            continue;
        }
        else {
            Node* new = (Node*)malloc(sizeof(Node));
            new->value = value;
            new->has_value = 1;
            new->next = NULL;
            node->next = new;
            return;
        }
    }
}

编译时建议加上-Wall -Wextra参数开启所有警告,编译器会直接提示存储局部变量地址这类常见错误,避免踩坑。


内容的提问来源于stack exchange,提问作者Christoffer Corfield Aakre

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:09:24