为何二叉树序列化反序列化代码在VSCode正常却在在线编辑器报运行时错误?
问题背景
实现二叉树的序列化与反序列化功能,本地VSCode测试运行正常,序列化结果为12345,反序列化后重新序列化结果仍为12345,但在线代码测试平台触发运行时错误,错误信息如下:
Address 0x7f547eb00021 is located in stack of thread T0 at offset 33 in frame
#0 0x40280f in helper /app/example.c:65
This frame has 1 object(s):
[32, 33) 'temp' (line 68) <== Memory access at offset 33 overflows this variable
错误信息含义解释
这段错误信息的中文翻译及核心含义:
地址0x7f547eb00021位于线程T0的栈帧中,偏移量33的位置
#0 错误发生在helper函数(对应代码第65行)
该栈帧中有一个变量:
占据内存区间[32,33)的temp变量(对应代码第68行)<== 访问偏移量33的内存超出了该变量的边界
简单来说:temp是单个char类型变量,在栈中仅占用1字节内存(左闭右开区间[32,33)),但代码访问了该区间之外的内存,触发了栈内存溢出。
代码问题根源
1. atoi(&temp)的越界访问
在helper函数中,你通过atoi(&temp)将单个字符转换为整数:
char temp = removeStr(Q); // ... new->val = atoi(&temp);
atoi函数要求输入是以\0结尾的C字符串,但temp是单个char变量,&temp指向的栈地址后没有自动补\0。atoi执行时会从temp的地址开始持续读取字节,直到遇到\0,这就会越界访问temp之后的栈内存,触发运行时错误。
本地测试未报错是巧合:你的本地栈中temp之后恰好有一个\0字节,atoi读取到此处停止,未触发越界检测;但在线平台的栈布局不同,temp之后没有\0,atoi继续读取超出变量范围的内存,触发了平台的内存保护机制。
2. 序列化/反序列化的逻辑错误(潜在问题)
当前序列化仅输出节点值,未记录空节点信息,但反序列化采用递归创建(类似前序遍历逻辑),会导致反序列化后的树结构与原树完全不同。比如原树为:
1 / \ 2 3 / \ 4 5
序列化结果是12345,反序列化后生成的树结构实际为:
1 / 2 / \ 3 4 / 5
只是恰好层序遍历结果仍为12345,导致你误以为结构正确,但实际逻辑存在严重错误。
修复方案
1. 修复atoi的越界问题
将单个字符转换为以\0结尾的字符串后再调用atoi:
struct TreeNode* helper(struct Queue* Q){ if(!isEmpty(Q)){ char temp = removeStr(Q); if(temp == '\0') return NULL; struct TreeNode* new = malloc(sizeof(struct TreeNode)); printf("\nChar : %c",temp); // 修复:创建临时字符串,确保以'\0'结尾 char buf[2] = {temp, '\0'}; new->val = atoi(buf); new->left = helper(Q); new->right = helper(Q); return new; } return NULL; }
2. 修复序列化/反序列化的逻辑错误
需要在序列化时用特殊字符(如#)标记空节点,反序列化时根据标记正确构建树:
- 修改
LOT函数(序列化):遇到空节点时写入# - 修改
helper函数(反序列化):读取到#时返回NULL,停止递归创建子节点
修改后的LOT函数示例:
void LOT(struct TreeNode* root,char* s){ struct Queue Q1; Q1.front = 0; Q1.rear = 0; int index = 0; addQueue(&Q1,root); while(!isEmpty(&Q1)){ int size = qSize(&Q1); for (int i = 0; i < size; i++) { root = removeQueue(&Q1); if(root == NULL){ s[index++] = '#'; continue; } s[index++] = root->val + '0'; addQueue(&Q1,root->left); addQueue(&Q1,root->right); } } s[index]='\0'; }
修改后的helper函数示例:
struct TreeNode* helper(struct Queue* Q){ if(isEmpty(Q)) return NULL; char temp = removeStr(Q); if(temp == '#' || temp == '\0') return NULL; struct TreeNode* new = malloc(sizeof(struct TreeNode)); char buf[2] = {temp, '\0'}; new->val = atoi(buf); new->left = helper(Q); new->right = helper(Q); return new; }
修改后,序列化结果会包含空节点标记(如原树的序列化结果为12##34##),反序列化能准确还原原树结构,同时彻底解决内存越界问题。
内容的提问来源于stack exchange,提问作者kuwira

