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

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所在的内存区域,导致其值被意外修改。

此外还有两个次要问题:

  1. 文件名检查逻辑错误:sizeof(argv[1])获取的是指针大小而非字符串长度,无法正确判断.asm后缀。
  2. 构建的字符串未添加'\0'终止符,会导致printf输出乱码。
  3. 内存释放不完整:仅释放了结构体的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:34:56