fread() 在升级 C++ 工具版本后无法正确读取结构数据

问题描述

我有一个较旧的项目,它使用 fread() 将二进制文件读入结构体。 它使用 Visual Studio 2017 (v141)

我将项目升级到最新的C++工具版本(v142),用于VS 2019,并将解决方案从AnyCPU更改为x86。

我还更改了结构成员对齐方式: 1 字节 (/Zp1) 至:默认

因为这个错误:

错误 C2338 Windows 标头需要默认打包选项。 更改此设置可能会导致内存损坏。这个诊断可以 通过定义 WINDOWS_IGNORE_PACKING_MISMATCH 的构建禁用。

但现在无法正确读取数据了。

这是我用来读取二进制文件的代码:

FILE* RecipeFile;
    short ReturnValue = 0; //AOK

    if ((RecipeFile = fopen(rcpFile,"rb")) == NULL) {
        ReturnValue = -1; //File open error
        return(ReturnValue);
    }

    long sizeOfItem;

    // obtain file size:
    fseek(RecipeFile,SEEK_END);
    sizeOfItem = ftell(RecipeFile); // gives 771088
    rewind(RecipeFile);

    //sizeOfItem = sizeof(recipe); // gives 824304??

    // now read the file contents:
    int noOfItemsRead = fread(&recipe,sizeOfItem,1,RecipeFile);

recipe 是一个结构体。见下文。

我注意到 sizeof(recipe) 给出的结果与文件大小不同。 使用工具集 141,我得到 798276 字节。 使用工具集 142,我得到 824304 字节。 实际文件大小为 771088 字节。

问题真的是结构成员对齐方式的变化引起的吗?

如何修复错误以便再次正确读取文件?

编辑: 我尝试将 pragma pack 指令添加到结构中,如下所示:

#pragma pack(push,1)
struct tRecipe {   
    RMPTableDescriptorType O2Flow;
    RMPTableDescriptorType HeFlow;
    RMPTableDescriptorType SiCl4Flow;
    RMPTableDescriptorType GeCl4Flow;
    RMPTableDescriptorType Extra_Flow;
    RMPTableDescriptorType POCl3Flow;
    RMPTableDescriptorType C2F6Flow;
    RMPTableDescriptorType SiF4Flow;
    RMPTableDescriptorType Cl2Flow;
    RMPTableDescriptorType BCl3Flow;
    RMPTableDescriptorType TTC_Temp;
    RMPTableDescriptorType TTC_H2Flow;
    RMPTableDescriptorType TTC_Ratio;
    RMPTableDescriptorType LCC_Speed;
    RMPTableDescriptorType LSC_Speed;
    RMPTableDescriptorType LTC_Speed;
    RMPTableDescriptorType TDS_Cursor;
    RMPTableDescriptorType TDC_Ctrl;
    RMPTableDescriptorType TPC_Ctrl;
    RMPTableDescriptorType TSS_Cursor;
    DLUTableDescriptorType DLU;
    EXHTableDescriptorType EXH;
    GENTableDescriptorType GEN;
    PARTableDescriptorType PAR;
    REFTableDescriptorType REF;
    SETTableDescriptorType SET;
    SUPTableDescriptorType SUP;
    TDCTableDescriptorType TDC;
    TDSTableDescriptorType TDS;
    TSSTableDescriptorType TSS;
    TTCTableDescriptorType TTC;
    TPCTableDescriptorType TPC;
};
#pragma pack(pop)

typedef struct
{
    RParam                      Value;
    UInt16                      Duration;
    OptType                     Opt;
#if DOS
    int                         Unused16BitVar; /* Established to allow Win32 NT Code to use 32bit */
#endif
}
RMPElementDescriptorType;

/*-----------------------------------------------------------------------------*/
typedef struct
{
    UInt16                      StartNoOf;
    UInt16                      EndNoOf;
    Char8                       StartRampType;
    Char8                       EndRampType;
    RMPElementDescriptorType    RMPElementArray[RAMP_ELEM_NO_OF];
}
RMPRampDescriptorType;

/*-----------------------------------------------------------------------------*/
typedef struct
{
    UInt16                 SizeInfo;
    Char8                  DBPTxtInfo[DBP_TXT_NO_OF];

    RMPRampDescriptorType  RMPRampArray[RAMP_NO_OF];
}
RMPTableDescriptorType;

但它似乎对错误没有任何影响。

解决方法

/Zp1 总是一个坏主意 - 它在全局范围内应用包装。通过更改包装,您会使现有文件不兼容。相反,您应该有选择地打包:

#pragma pack(1) // 1 byte packing
struct sRecipe
{
   ...
} ;
#pragma pack() // restore default packing

然而,这可能无法解决编译器、编译器版本和目标之间的所有兼容性问题(并且您还将目标从 AnyCPU 更改为 x86)。实际上,最好使用 CSV、XML 或具有“网络字节顺序”的二进制文件(使用 htonl()/nltoh() et al)序列化/反序列化保存到文件中的数据例如,为了避免原始结构的对齐和字节顺序问题。

本质上,您应该编写代码来显式生成您为文件指定的格式,二进制文件逐字节生成,否则使用明确的字符串表示。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...