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

C语言修改文件内struct变量时累加数据未写入文件问题

问题描述

实现用户数据消耗量跟踪逻辑,规则为:指定日期下对应客户端如果在文件中已有消耗记录,将新产生的消耗值累加至原有记录。目前客户端消耗可正常跟踪捕获,但累加值无法写入文件。经排查CargaConsumoExistente函数可成功定位目标记录、完成内存层面的数值累加,但修改后的结构体内容未实际写入文件。

问题相关代码如下:

void CargaArchivoConsumo(char ArchivoConsumo[])
///Add clients struct to file
{
    stConsumo c;
    char opcion;
    int idCon=0, id=1;
    FILE *archi=fopen(ArchivoConsumo,"ab");
    if(archi)
    {
        id=idConsumoUnico("consumo.dat");
        do
        {
            c=cargaConsumo();
            c.id=id;
            idCon=BuscaIDConsumo("consumo.dat",c.fecha,c.idCliente);
///this function finds out if this particular user already has data consumption on this date, in which case it returns that consuption's ID.
            if (idCon!=0)
            {
                CargaConsumoExistente("consumo.dat",idCon,c.datosConsumidos);
///passes repeated record id and new data consumption onto this function, which SHOULD add it up to the previous record. 
                printf("\nYa se cargo un consumo para este usuario en esa fecha. El nuevo consumo se sumara al anterior\n");
            }
            else
            { 
/// If no other record is found, the new struct is written onto the file.
                fwrite(&c,sizeof(stConsumo), 1, archi);
                id++;
            }
            printf("\nPresione ESC para salir, cualquier tecla para continuar");
            fflush(stdin);
            opcion=getch();
            system("cls");
        }
        while(opcion!=ESC);
        fclose(archi);

    }
}

void CargaConsumoExistente(char ArchivoConsumo[],int idCon,int datos) 
///Adds new data on to the old record. Receives record ID and new data as parameter.
{
    stConsumo c;
    int found=0;
    FILE *archi= fopen(ArchivoConsumo, "r+");
    if(archi)
    {
        while (found==0 && fread(&c,sizeof(stConsumo),1,archi)>0)
        {///searching for that specific id on the file, and when found adds the new data onto the record.
            if(idCon==c.id)
            {
                c.datosConsumidos+=datos;           
                fwrite(&c,sizeof(stConsumo),1,archi);
                found=1;
            }
        }
        fclose(archi);
    }
}
问题根因

写入失败由三个直接问题导致:

  • 文件锁冲突:CargaArchivoConsumo启动后就以"ab"追加模式常驻打开consumo.dat,Windows平台下文件被追加模式句柄占用时,其他位置以"r+"模式打开的写入操作会被系统拦截,根本不会执行落盘。
  • 文件指针偏移错误:CargaConsumoExistente中fread成功读取到匹配的记录后,文件指针已经移动到该条记录的末尾、下一条记录的起始位置,此时直接调用fwrite会把修改后的数据写到下一条记录的位置,要么覆盖其他有效数据,要么因为偏移位置错误无法覆盖原记录。
  • 二进制读写模式错误:Windows平台下操作二进制结构体文件用"r+"文本模式,会触发换行符自动转义逻辑,导致结构体长度计算错位,读写位置偏移。
修复方案
  1. 移除CargaArchivoConsumo开头常驻的文件句柄,仅在需要写入新记录时以追加模式打开文件,写完立即关闭,避免多句柄文件锁冲突。
  2. 在CargaConsumoExistente中找到匹配记录、修改内存值后,先通过fseek将文件指针回退一个stConsumo结构体长度,定位到当前记录的起始位置,再执行写入操作。
  3. 二进制文件读写统一使用带b标识的打开模式,随机读写用"r+b",追加写用"ab",避免文本模式转义导致的错位。

修复后可正常运行的代码如下:

void CargaArchivoConsumo(char ArchivoConsumo[])
///Add clients struct to file
{
    stConsumo c;
    char opcion;
    int idCon=0, id=1;
    id=idConsumoUnico("consumo.dat");
    do
    {
        c=cargaConsumo();
        c.id=id;
        idCon=BuscaIDConsumo("consumo.dat",c.fecha,c.idCliente);
        if (idCon!=0)
        {
            CargaConsumoExistente("consumo.dat",idCon,c.datosConsumidos);
            printf("\nYa se cargo un consumo para este usuario en esa fecha. El nuevo consumo se sumara al anterior\n");
        }
        else
        { 
            // 仅在需要写新记录时打开文件,写完立即关闭
            FILE *archi=fopen(ArchivoConsumo,"ab");
            if(archi)
            {
                fwrite(&c,sizeof(stConsumo), 1, archi);
                fclose(archi);
                id++;
            }
        }
        printf("\nPresione ESC para salir, cualquier tecla para continuar");
        fflush(stdin);
        opcion=getch();
        system("cls");
    }
    while(opcion!=ESC);
}

void CargaConsumoExistente(char ArchivoConsumo[],int idCon,int datos) 
///Adds new data on to the old record. Receives record ID and new data as parameter.
{
    stConsumo c;
    FILE *archi= fopen(ArchivoConsumo, "r+b"); // 二进制读写模式必须加b
    if(archi)
    {
        while (fread(&c,sizeof(stConsumo),1,archi)>0)
        {
            if(idCon==c.id)
            {
                c.datosConsumidos+=datos;
                // 回退文件指针到当前记录起始位置
                fseek(archi, -sizeof(stConsumo), SEEK_CUR);
                fwrite(&c,sizeof(stConsumo),1,archi);
                break; // 找到目标后直接退出循环,无需继续遍历
            }
        }
        fclose(archi);
    }
}
开发注意事项
  • 操作二进制文件时,所有打开模式必须加b后缀,避免不同平台下的文本转义逻辑导致数据错位。
  • 尽量不要同时持有同一个文件的多个打开句柄,尤其是不同打开模式的句柄,极易触发文件锁、指针位置混乱问题,文件操作完成后立即关闭句柄。
  • 二进制文件随机读写时,fread、fwrite执行完成后会自动向后移动文件指针,读写切换前必须通过fseek/ftell确认指针位置,不要默认指针停在预期位置。

内容的提问来源于stack exchange,提问作者Tomás Temperley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:48:16