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

C语言fscanf读文件到struct触发Access Violation错误

问题背景

编写基础C语言程序实现文本文件与结构体的信息读写功能:结构体全量数据写入文本文件的函数可正常运行,但将文件中同格式信息读取回空结构体时,持续触发Access Violation(访问冲突)错误,程序崩溃返回码为0xC0000005。

原始实现代码
#include <stdio.h>
#include <stdlib.h>

#define MAXENTRIES 100

struct entry{
    unsigned int recordNum;
    char *toolName;
    unsigned int quantity;
    double cost;
};
struct entry dataBase[MAXENTRIES];
int currentEntries = 0;

void initDataBase(void){
    dataBase[0].recordNum = 3;
    dataBase[0].toolName = "Electric Sander";
    dataBase[0].quantity = 7;
    dataBase[0].cost = 57.98;

    dataBase[1].recordNum = 17;
    dataBase[1].toolName = "Hammer";
    dataBase[1].quantity = 76;
    dataBase[1].cost = 11.99;

    dataBase[2].recordNum = 24;
    dataBase[2].toolName = "Jig Saw";
    dataBase[2].quantity = 21;
    dataBase[2].cost = 11.00;

    dataBase[3].recordNum = 39;
    dataBase[3].toolName = "Lawn Mower";
    dataBase[3].quantity = 3;
    dataBase[3].cost = 79.50;

    dataBase[4].recordNum = 56;
    dataBase[4].toolName = "Power Saw";
    dataBase[4].quantity = 18;
    dataBase[4].cost = 99.99;

    dataBase[5].recordNum = 68;
    dataBase[5].toolName = "Screwdriver";
    dataBase[5].quantity = 106;
    dataBase[5].cost = 6.99;

    dataBase[6].recordNum = 77;
    dataBase[6].toolName = "Sledge Hammer";
    dataBase[6].quantity = 11;
    dataBase[6].cost = 21.50;

    dataBase[7].recordNum = 83;
    dataBase[7].toolName = "Wrench";
    dataBase[7].quantity = 34;
    dataBase[7].cost = 7.50;

    currentEntries = 8;
}

void printEntry(int i){
    printf("\nRecordNum : %d", dataBase[i].recordNum);
    printf("\nTool Name : %s", dataBase[i].toolName);
    printf("\nQuantity  : %d", dataBase[i].quantity);
    printf("\nCost      : %.2f\n\n", dataBase[i].cost);
}

void fprintDataBase(void){
    FILE *dbPtr;
    if((dbPtr = fopen("hardware.dat", "w")) == NULL){
        puts("File could not be opened");
    }
    else{
        for(int i = 0; i < currentEntries; i++){
                fprintf(dbPtr, "%d %s %d %.2f\n", dataBase[i].recordNum, dataBase[i].toolName, dataBase[i].quantity, dataBase[i].cost);
        }
        printf("\nDatabase recorded!\n");
        fclose(dbPtr);
    }
}

void fscanDataBase(void){
    FILE *dbPtr;
    if((dbPtr = fopen("hardware.dat", "r")) == NULL){
        puts("File could not be opened");
    }
    else{
        currentEntries = 1;
        fscanf(dbPtr, "%d", &dataBase[0].recordNum);       //Access Violation Error HERE

        while(!feof(dbPtr)){
            fscanf(dbPtr, "%d%29s%d%lf", &dataBase[currentEntries].recordNum, dataBase[currentEntries].toolName, &dataBase[currentEntries].quantity, &dataBase[currentEntries].cost);
            currentEntries++;
        }
        printf("\nDatabase scanned in!\n");
        fclose(dbPtr);
    }
}

int main()
{
//    initDataBase();
//    fprintDataBase();
    fscanDataBase();
    printEntry(0);

    return 0;
}
复现规律
  • 调用initDataBase()完成初始化后执行fprintDataBase(),可正常生成包含全部所需数据的hardware.dat文件
  • 注释掉初始化、写文件的两行调用,单独执行fscanDataBase()读取文件就触发访问冲突崩溃
  • 崩溃点标注为fscanDataBase()中读取第一条记录编号的fscanf行
已排除的故障可能
  • 数组越界:initDataBase()运行时可正常访问dataBase[0]元素
  • 结构体作用域问题:dataBase为全局变量,不存在作用域失效情况
  • 基础语法问题:initDataBase()中测试用scanf写入dataBase[0]字段可正常执行
故障根因
  1. 核心触发原因:野指针写入
    结构体的toolName是char*类型指针,没有预先分配可写的有效内存:
  • 执行initDataBase()时,代码直接把toolName指针指向了只读字符串常量的地址,此时指针是有效的,所以写文件操作正常
  • 跳过初始化直接读文件时,全局数组的指针成员默认初始值为NULL(无效内存地址),后续fscanf尝试把读取到的工具名写入NULL指向的内存段,直接触发内存访问违规。哪怕代码里先读recordNum不触发,执行到读toolName的逻辑时一样会崩溃。
  1. 附带逻辑缺陷
  • %s格式符读取字符串时会在第一个空白字符处截断,Electric Sander这类带空格的工具名无法被完整读取,还会打乱后续字段的读取偏移
  • while(!feof(dbPtr))的循环写法存在逻辑bug,会在文件读取结束后多执行一次读操作,可能产生冗余无效记录甚至触发数组越界
  • 文件打开失败的分支没有加return,会继续执行后续文件操作逻辑
修复方案
  1. 新增依赖头文件,修改结构体定义,将字符指针替换为固定长度字符数组,从根源解决指针内存无效问题:
#include <string.h>
#include <ctype.h>

#define MAXENTRIES 100
#define NAMELEN 30 // 定义工具名最大长度
struct entry{
    unsigned int recordNum;
    char toolName[NAMELEN]; // 替换原char*,数组自带预分配的可写存储空间
    unsigned int quantity;
    double cost;
};
  1. 重写文件读取逻辑,修正格式匹配、循环判断的bug:
void fscanDataBase(void){
    FILE *dbPtr;
    if((dbPtr = fopen("hardware.dat", "r")) == NULL){
        puts("File could not be opened");
        return; // 打开失败直接退出,不执行后续逻辑
    }
    currentEntries = 0;
    // 以fscanf返回值作为循环判断:成功读满4个字段才处理,避免feof的多读问题
    while(fscanf(dbPtr, "%u %29[^0-9] %u %lf", 
        &dataBase[currentEntries].recordNum,
        dataBase[currentEntries].toolName,
        &dataBase[currentEntries].quantity,
        &dataBase[currentEntries].cost) == 4){
        // 清除工具名末尾因为格式匹配产生的多余空格
        char *tail = dataBase[currentEntries].toolName + strlen(dataBase[currentEntries].toolName) - 1;
        while(tail >= dataBase[currentEntries].toolName && isspace(*tail)){
            *tail = '\0';
            tail--;
        }
        currentEntries++;
        if(currentEntries >= MAXENTRIES) break; // 防止超过数组最大容量
    }
    printf("\nDatabase scanned in! total %d entries\n", currentEntries);
    fclose(dbPtr);
}
  1. 同步调整初始化函数的赋值逻辑,因为toolName改成了数组,不能直接给指针赋值,要用strcpy拷贝字符串,其余条目按相同规则替换即可:
void initDataBase(void){
    dataBase[0].recordNum = 3;
    strcpy(dataBase[0].toolName, "Electric Sander");
    dataBase[0].quantity = 7;
    dataBase[0].cost = 57.98;

    dataBase[1].recordNum = 17;
    strcpy(dataBase[1].toolName, "Hammer");
    dataBase[1].quantity = 76;
    dataBase[1].cost = 11.99;
    // 剩余初始化条目省略
    currentEntries = 8;
}

内容的提问来源于stack exchange,提问作者Owen Gibson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 03:18:29