C语言实现链表字符列表程序时exit条件分支失效问题排查
C语言链表列表程序exit指令异常问题分析
问题现象
本程序为基于单链表实现的字符列表管理工具,核心支持4类指令:
insert:向链表尾部添加合法小写英文字符remove:遍历释放所有链表节点,清空整个列表print:按顺序输出当前列表存储的所有字符exit:终止程序运行
程序存在可稳定复现的异常:
程序启动后第一次输入
exit可以正常触发退出逻辑,但只要先后执行过insert/remove/exit就始终无法匹配到退出分支,程序无法正常终止;如果将exit的判断逻辑从“对比栈上定义的exits字符数组”修改为“直接对比字符串字面量"exit"”,异常会立刻消失。
根因定位
异常的核心触发原因是代码存在栈缓冲区越界写,直接破坏了栈上存储的exits指令字符串内容,完整的触发链路如下:
- 存在错误的输入逻辑
在insert指令的处理分支中,接收用户输入的代码存在明确缺陷:// main函数内定义的单字符变量 char input; // ... // insert分支中的输入逻辑 printf("Enter a char: "); scanf("%s", &input);input是仅占1字节的单字符变量,没有为字符串预留专门的结束符存储位置;但%s格式的scanf会将读取到的连续非空白字符依次写入目标内存,并在写入内容的末尾自动追加1字节的'\0'作为字符串结束符,全程不做任何写入边界检查。 - 越界写的精准破坏效果
主流编译器对栈上局部变量的排布遵循统一规则:先定义的变量分配在更高的栈地址,后定义的变量分配在更低的栈地址。对照代码中main函数内的变量定义顺序,input变量的栈位置恰好与exits字符数组相邻,两者的内存地址差为1字节:- 程序刚启动时,栈上的
exits数组存储内容为"exit\0",和用户输入的exit字符串完全一致,strcmp比对结果为0,因此第一次输入exit可以正常进入退出分支 - 只要执行过一次
insert分支,scanf的越界写操作就会将exits数组的首个字节(原本存储字符'e'的位置)覆盖为'\0',此时栈上的exits数组已经退化为长度为0的空字符串 - 后续执行
strcmp(command, exits)时,用户输入的exit命令和空字符串比对,结果永远不会为0,自然无法匹配到退出分支;而inserts/removes/prints三个指令字符串不在越界写的覆盖路径上,内容始终保持正常,因此对应指令的匹配逻辑不受影响,和问题表现完全吻合。
- 程序刚启动时,栈上的
- 修改判断逻辑后恢复正常的原因
直接和字符串字面量"exit"做比对时,该字面量存储在程序的只读常量内存区,与栈内存完全隔离,栈上的越界写操作无法修改常量区的固定内容,因此字符串比对逻辑始终可以正常工作。
代码中存在的其他连带问题
char command[] = "";仅为command数组分配1字节的存储空间,用%s读取任意长度的指令时必然存在栈越界风险,应调整为足够长度的可读写数组,例如char command[16];- 对
command、input使用%s格式读取时未限制最大输入长度,进一步放大了缓冲区溢出的风险 - insert函数中的
x = ("%d", input);是逗号表达式,并非有效的字符类型转换,实际执行效果等价于x = input,虽偶然能得到正确结果,但写法完全不符合语法逻辑 - 栈上存储多个指令字符串数组既浪费栈空间,也容易因越界写破坏指令内容,直接使用字面量做比对更安全。
内容的提问来源于stack exchange,提问作者Nicolás Ortíz
相关产品推荐
相关产品推荐

