【问题标题】:Read from binary file to array: Preceding arbitrary numbers从二进制文件读取到数组:前面的任意数字
【发布时间】:2017-05-15 02:40:11
【问题描述】:

我正在尝试从二进制文件读取字符数组。打印数组条目时,将打印任意数字(换行符)和所需的数字。我真的无法理解这一点。 文件的前几个字节是: 00 00 08 03 00 00 EA 60 00 00 00 1C 00 00 00 1C 00 00

我的代码:

  void MNISTreader::loadImagesAndLabelsToMemory(std::string imagesPath,
                          std::string labelsPath) {
  std::ifstream is(imagesPath.c_str());
  char *data = new char[12];

  is.read(data, 12);

  std::cout << std::hex  << (int)data[2] << std::endl;

  delete [] data;
  is.close();
}

例如它打印:

ffffff9b
8

8 是正确的。前面的数字随着执行而变化。这个换行符是从哪里来的?

【问题讨论】:

  • 您希望(int)data[2] 做什么?您是否有理由使用data 可能已签名或未签名的类型?
  • @DavidSchwartz 我希望将 data[2] 打印为整数,而不是字符。
  • 啊,但是您想将二进制文件解释为可能会或可能不会签名的字符,具体取决于实现? (如果不是,为什么char *data; 后跟is.read(data, 12);?)提示:试试unsigned char* data...
  • @DavidSchwartz 我已经尝试过使用无符号字符数组,但没有任何区别。我可能对如何存储数据没有足够的了解。我就是这么想的:我想从文件中读取 8 位值,所以我将使用某种字符,因为它应该是 8 位宽。我不知道有什么区别会将其存储在无符号字符数组中。我假设从文件中读取的位串不会改变..但这可能是错误的。
  • @NarcEritor 仅凭您向我们展示的代码很难说。给我们足够的代码来重现问题。

标签: c++ arrays ifstream


【解决方案1】:

您询问了从二进制文件中读取数据并将其保存到 char[] 中的问题,并且您向我们展示了您提交的问题代码:

  void MNISTreader::loadImagesAndLabelsToMemory(std::string imagesPath,
                          std::string labelsPath) {
  std::ifstream is(imagesPath.c_str());
  char *data = new char[12];

  is.read(data, 12);

  std::cout << std::hex  << (int)data[2] << std::endl;

  delete [] data;
  is.close();
}

而你想知道:

前面的数字随着执行的变化而变化。这个换行符是从哪里来的?

在您真正回答这个问题之前,您需要了解二进制文件。这就是内部文件的结构。当您从二进制文件中读取数据时,您必须记住某些程序已将数据写入该文件,并且该数据是以结构化格式写入的。正是这种格式对于每个系列或二进制文件类型来说都是唯一的,这一点很重要。大多数二进制文件通常会遵循一个共同的模式,这样它们会包含一个header,然后甚至可能是sub headers,然后是集群、数据包或块等,甚至是标头之后的原始数据,而一些二进制文件可能只是纯粹的原始数据.您必须知道文件在内存中的结构。

  • 数据的结构是什么?
    • 文件第一个条目的数据类型是char = 1 byteint = 4 bytes (32bit system) 8 bytes (64bit system)float = 4bytesdouble = 8bytes等吗?

根据您的代码,您有一个chararray,其大小为12,并且知道char 在您要求12 bytes 的内存中是1 byte。现在这里的问题是,您连续提取 12 个连续的单个字节,并且由于不知道文件结构,您如何确定第一个字节是实际写入的 char 还是 unsigned char,还是 int ?

考虑由C++ structs 创建的这两个不同的二进制文件结构,其中包含所有需要的data,并且都以二进制格式写入文件。

两个文件结构都将使用的通用标题结构。

struct Header {
    // Size of Header
    std::string filepath;
    std::string filename;

    unsigned int pathSize;
    unsigned int filenameSize;

    unsigned int headerSize;
    unsigned int dataSizeInBytes;
};

FileA 文件 A 的唯一结构

struct DataA {
    float width;
    float length;
    float height;
    float dummy; 
}

FileB 文件 B 的唯一结构

struct DataB {
    double length;
    double width;
}

内存中的文件通常是这样的:

  • 前几个字节是路径和文件名以及存储的大小
    • 这可能因文件而异,具体取决于字符数 用于文件路径和文件名。
    • 在字符串之后,我们确实知道接下来的 4 种数据类型是无符号的 所以我们知道在 32 位系统上它将是 4bytes x 4 = 16 total bytes
    • 对于 64 位系统,它将是 8 字节 x 4 = 32 总字节。
    • 如果我们了解系统架构,那么我们可以很容易地解决这个问题。
    • 在这 4 个无符号中,前两个用于路径和文件名的长度。现在这些可能是从文件中读取的前两个,而不是实际路径。这些顺序可以颠倒。
    • 重要的是接下来的 2 个无符号
    • 下一个是完整大小的标头,可用于读入和跳过标头。
    • 下一个告诉您现在要提取的数据的大小,这些数据可以是块,并计算有多少块,因为它可能是一系列相同的数据结构,但为简单起见,我省略了块和计数并使用单个实例结构。
    • 在这里,我们可以根据要提取的字节数提取数据量(以字节为单位)。

让我们考虑两个不同的二进制文件,我们已经通过了所有的头信息,并且正在读取要解析的字节。我们得到以字节为单位的数据大小,对于FileA,我们有4 floats = 16bytes,对于FileB,我们有2 doubles = 16bytes。所以现在我们知道如何调用该方法来读取x 的数据量以获取y 类型的数据。因为y 现在是typex 是数量,我们可以这样说:y(x) 好像y 是内置类型,x 是默认内置类型的数字初始化器此内置类型的构造函数可以是intfloatdoublechar 等。

现在假设我们正在读取这两个文件中的任何一个,但不知道数据结构以及之前如何将其信息存储到文件中,并且我们通过标题看到数据大小为16 bytes in内存,但我们不知道它是存储为4 floats = 16 bytes 还是2 doubles = 16 bytes。两种结构都是 16 字节,但具有不同数量的不同数据类型。

总结就是,不知道文件的数据结构,不知道怎么解析二进制确实变成了X/Y Problem

现在让我们假设您确实知道要尝试从上面回答您的问题的文件结构,您可以尝试这个小程序并查看一些结果:

#include <string>
#include <iostream>

int main() {

    // Using Two Strings
    std::string imagesPath("ImagesPath\\");
    std::string labelsPath("LabelsPath\\");

    // Concat of Two Strings
    std::string full = imagesPath + labelsPath;

    // Display Of Both
    std::cout << full << std::endl;

    // Data Type Pointers 
    char* cData = nullptr;
    cData = new char[12];

    unsigned char* ucData = nullptr;
    ucData = new unsigned char[12];

    // Loop To Set Both Pointers To The String
    unsigned n = 0;
    for (; n < 12; ++n) {
        cData[n] = full.at(n);
        ucData[n] = full.at(n);
    }

    // Display Of Both Strings By Character and Unsigned Character
    n = 0;
    for (; n < 12; ++n) {
        std::cout << cData[n];
    }
    std::cout << std::endl;

    n = 0;
    for (; n < 12; ++n) {
        std::cout << ucData[n];
    }
    std::cout << std::endl;
    // Both Yeilds Same Result
    // Okay lets clear out the memory of these pointers and then reuse them.

    delete[] cData;
    delete[] ucData;
    cData = nullptr;
    ucData = nullptr;

    // Create Two Data Structurs 1 For Each Different File
    struct A {
        float length;
        float width;
        float height;
        float padding;
    };

    struct B {
        double length;
        double width;
    };

    // Constants For Our Data Structure Sizes
    const unsigned sizeOfA = sizeof(A);
    const unsigned sizeOfB = sizeof(B);

    // Create And Populate An Instance Of Each
    A a;
    a.length = 3.0f;
    a.width = 3.0f;
    a.height = 3.0f;
    a.padding = 0.0f;

    B b;
    b.length = 5.0;
    b.width = 5.0;

    // Lets First Use The `Char[]` Method for each struct and print them
    // but we need 16 bytes instead of `12` from your problem
    char *aData = nullptr;  // FileA
    char *bData = nullptr;  // FileB

    aData = new char[16];
    bData = new char[16];

    // Since A has 4 floats we know that each float is 4 and 16 / 4 = 4
    aData[0] = a.length;
    aData[4] = a.width;
    aData[8] = a.height;
    aData[12] = a.padding;

    // Print Out Result but by individual bytes without casting for A
    // Don't worry about the compiler warnings and build and run with the
    // warning and compare the differences in what is shown on the screen 
    // between A & B.

    n = 0;
    for (; n < 16; ++n) {
        std::cout << aData[n] << " ";
    }
    std::cout << std::endl;

    // Since B has 2 doubles weknow that each double is 8 and 16 / 8 = 2
    bData[0] = b.length;
    bData[8] = b.width;

    // Print out Result but by individual bytes without casting for B
    n = 0;
    for (; n < 16; ++n) {
        std::cout << bData[n] << " ";
    }
    std::cout << std::endl;

    // Let's Print Out Both Again But By Casting To Their Approriate Types
    n = 0;
    for (; n < 4; ++n) {
        std::cout << reinterpret_cast<float*>(aData[n]) << " ";
    }
    std::cout << std::endl;

    n = 0;
    for (; n < 2; ++n) {
        std::cout << reinterpret_cast<double*>(bData[n]) << " ";
    }
    std::cout << std::endl;

    // Clean Up Memory
    delete[] aData;
    delete[] bData;
    aData = nullptr;
    bData = nullptr;

    // Even By Knowing The Appropriate Sizes We Can See A Difference
    // In The Stored Data Types. We Can Now Do The Same As Above
    // But With Unsigned Char & See If It Makes A Difference.

    unsigned char *ucAData = nullptr;
    unsigned char *ucBData = nullptr;

    ucAData = new unsigned char[16];
    ucBData = new unsigned char[16];

    // Since A has 4 floats we know that each float is 4 and 16 / 4 = 4
    ucAData[0] = a.length;
    ucAData[4] = a.width;
    ucAData[8] = a.height;
    ucAData[12] = a.padding;

    // Print Out Result but by individual bytes without casting for A
    // Don't worry about the compiler warnings and build and run with the
    // warning and compare the differences in what is shown on the screen 
    // between A & B.

    n = 0;
    for (; n < 16; ++n) {
        std::cout << ucAData[n] << " ";
    }
    std::cout << std::endl;

    // Since B has 2 doubles weknow that each double is 8 and 16 / 8 = 2
    ucBData[0] = b.length;
    ucBData[8] = b.width;

    // Print out Result but by individual bytes without casting for B
    n = 0;
    for (; n < 16; ++n) {
        std::cout << ucBData[n] << " ";
    }
    std::cout << std::endl;

    // Let's Print Out Both Again But By Casting To Their Approriate Types
    n = 0;
    for (; n < 4; ++n) {
        std::cout << reinterpret_cast<float*>(ucAData[n]) << " ";
    }
    std::cout << std::endl;

    n = 0;
    for (; n < 2; ++n) {
        std::cout << reinterpret_cast<double*>(ucBData[n]) << " ";
    }
    std::cout << std::endl;

    // Clean Up Memory
    delete[] ucAData;
    delete[] ucBData;
    ucAData = nullptr;
    ucBData = nullptr;

    // So Even Changing From `char` to an `unsigned char` doesn't help here even
    // with reinterpret casting. Because These 2 Files Are Different From One Another.
    // They have a unique signature. Now a family of files where a specific application
    // saves its data to a binary will all follow the same structure. Without knowing
    // the structure of the binary file and knowing how much data to pull in and the big key
    // word here is `what type` of data you are reading in and by how much. This becomes an (X/Y) Problem.
    // This is the hard part about parsing binaries, you need to know the file structure. 

    char c = ' ';
    std::cin.get(c);

    return 0;
}

运行上面的小程序后,不用担心屏幕上显示的每个值是什么;只需查看用于比较两种不同文件结构的模式即可。这只是为了表明 struct of floats 的宽度为 16 bytesstruct of doubles 的宽度也为 16 bytes 不同。因此,当我们回到您的问题并且您正在阅读12 individual consecutive bytes 时,问题就变成了这些第一个12 bytes 代表什么?如果在 32 位机器上是 3 ints3 unsigned ints,在 64 位机器上是 2 ints2 unsigned ints,还是 3 floats,或者是 2 doubles1 float 之类的组合?您正在读取的二进制文件的当前数据结构是什么?

编辑在我写的小程序中;我确实忘记尝试或在打印输出语句中添加&lt;&lt; std::hex &lt;&lt;,它们也可以添加到索引指针的每次打印中,但没有必要这样做,因为显示器的输出是相同的确切的东西,因为这只是直观地显示或表达内存中两种数据结构的差异以及它们的模式是什么样的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    相关资源
    最近更新 更多