C语言中创建新字符串为何会意外修改旧字符串的值?
问题分析与修复:HACK汇编器字符串处理BUG
问题背景
正在进行nand2tetris第6个项目——开发HACK汇编器,需求是生成存储汇编文件数据的大字符串,需去除Windows下的0x0d字符及连续换行(避免0x0a紧跟0x0a)。调试时发现,执行new_string->length = 1;语句时,意外修改了the_old_string的值。
头文件 assembler.h
typedef struct string { int length; char *the_string; } string;
实现代码
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <iso646.h> #include <string.h> #include "assembler.h" string *put_the_entire_file_into_one_big_string(FILE *the_entire_file); string *take_out_the_double_lines(string *the_string); int main(int argc, char *argv[]) { // Check the number of arguments if (argc != 2) { printf("Provide only one argument: the name of the assembly file\n"); return -1; } // Check if the argument is an .asm file int a = sizeof(argv[1]); if (!((argv[1][a - 5] == '.') and (argv[1][a - 4] == 'a') and (argv[1][a - 3] == 's') and (argv[1][a - 2] == 'm'))) { printf("not an .asm file\n"); return -1; } // Check if the file exists FILE *the_file = fopen(argv[1], "r"); if (the_file == NULL) { printf("File doesn't exist. Maybe you mistyped it?"); return -1; } string *the_big_string_thats_the_entire_file = put_the_entire_file_into_one_big_string(the_file); string *the_big_string_but_without_mulitple_line_breaks_in_a_row = take_out_the_double_lines(the_big_string_thats_the_entire_file); free(the_big_string_thats_the_entire_file->the_string); printf("%s", the_big_string_but_without_mulitple_line_breaks_in_a_row->the_string); free(the_big_string_but_without_mulitple_line_breaks_in_a_row->the_string); fclose(the_file); } string *put_the_entire_file_into_one_big_string(FILE *the_entire_file) { string *one_big_string; one_big_string->the_string = malloc(sizeof(char)); one_big_string->length = 0; char c; while (1) { c = fgetc(the_entire_file); if (c == EOF) { break; } if (c == 0x0d) { continue; } one_big_string->the_string[one_big_string->length] = c; one_big_string->length++; one_big_string->the_string = realloc(one_big_string->the_string, sizeof(char) * (one_big_string->length + 1)); } return one_big_string; } string *take_out_the_double_lines(string *the_old_string) { string *new_string; new_string->the_string = malloc(sizeof(char)); new_string->length = 1; new_string->the_string[0] = the_old_string->the_string[0]; new_string->length = 2; new_string->the_string = realloc(new_string->the_string, sizeof(char) * 2); for (int i = 1; i < the_old_string->length; i++) { printf("%i %i\n", i, the_old_string->length); if (not(the_old_string->the_string[i] == 0x0a and the_old_string->the_string[i - 1] == 0x0a)) { new_string->the_string[i] = the_old_string->the_string[i]; new_string->length++; new_string->the_string = realloc(new_string->the_string, sizeof(char) * new_string->length); } } return new_string; }
问题根源
核心BUG是未初始化结构体指针:
- 在
put_the_entire_file_into_one_big_string和take_out_the_double_lines函数中,仅声明了string类型指针,但未为结构体本身分配内存,直接访问->length和->the_string属于未定义行为。 - 未初始化的指针指向随机内存地址,赋值操作可能刚好覆盖
the_old_string所在的内存区域,导致其值被意外修改。
此外还有两个次要问题:
- 文件名检查逻辑错误:
sizeof(argv[1])获取的是指针大小而非字符串长度,无法正确判断.asm后缀。 - 构建的字符串未添加
'\0'终止符,会导致printf输出乱码。 - 内存释放不完整:仅释放了结构体的
the_string成员,未释放结构体本身,造成内存泄漏。
修复方案
1. 初始化结构体指针
在使用结构体指针前,先为结构体分配内存:
// 在put_the_entire_file_into_one_big_string函数中 string *one_big_string = malloc(sizeof(string)); if (one_big_string == NULL) { perror("malloc failed"); exit(EXIT_FAILURE); }
// 在take_out_the_double_lines函数中 string *new_string = malloc(sizeof(string)); if (new_string == NULL) { perror("malloc failed"); exit(EXIT_FAILURE); }
2. 修复文件名检查逻辑
用strlen获取字符串长度,结合strcmp判断后缀:
// 替换原文件名检查代码 size_t len = strlen(argv[1]); if (len < 4 || strcmp(argv[1] + len - 4, ".asm") != 0) { printf("not an .asm file\n"); return -1; }
3. 优化字符串构建逻辑
避免循环调用realloc,先计算目标长度再一次性分配内存:
string *take_out_the_double_lines(string *the_old_string) { string *new_string = malloc(sizeof(string)); if (new_string == NULL) { perror("malloc failed"); exit(EXIT_FAILURE); } // 计算需要保留的字符数 int new_len = 0; char prev = '\0'; for (int i = 0; i < the_old_string->length; i++) { char curr = the_old_string->the_string[i]; if (!(curr == '\n' && prev == '\n')) { new_len++; prev = curr; } } // 分配内存并复制字符 new_string->the_string = malloc(new_len + 1); // +1 存储终止符 if (new_string->the_string == NULL) { perror("malloc failed"); free(new_string); exit(EXIT_FAILURE); } new_string->length = new_len; int idx = 0; prev = '\0'; for (int i = 0; i < the_old_string->length; i++) { char curr = the_old_string->the_string[i]; if (!(curr == '\n' && prev == '\n')) { new_string->the_string[idx++] = curr; prev = curr; } } new_string->the_string[idx] = '\0'; // 添加字符串终止符 return new_string; }
4. 补充字符串终止符
在put_the_entire_file_into_one_big_string函数末尾添加终止符:
one_big_string->the_string[one_big_string->length] = '\0';
5. 完善内存释放
释放结构体本身,避免内存泄漏:
// 在main函数中 free(the_big_string_thats_the_entire_file->the_string); free(the_big_string_thats_the_entire_file); // 释放结构体 free(the_big_string_but_without_mulitple_line_breaks_in_a_row->the_string); free(the_big_string_but_without_mulitple_line_breaks_in_a_row); // 释放结构体
内容的提问来源于stack exchange,提问作者username
相关产品推荐
相关产品推荐

