【问题标题】:Understanding binary conversions了解二进制转换
【发布时间】:2013-02-04 03:52:07
【问题描述】:

我正在编写一个资源文件,我想从各种常见文件(例如 .JPG、.BMP)中插入一堆数据,并且我希望它是二进制文件。 我将编写一些代码来稍后按索引组织检索这些数据,这就是我目前得到的:

float randomValue = 23.14f;

ofstream fileWriter;
fileWriter.open("myFile.dat", ios::binary);
fileWriter.write((char*)&randomValue, sizeof(randomValue));
fileWriter.close();
//With this my .dat file, when opened in notepad has "B!¹A" in it

float retrieveValue = 0.0f;

ifstream fileReader;
fileReader.open("myFile.dat", ios::binary);
fileReader.read((char*)&retrieveValue, sizeof(retrieveValue));
fileReader.close();

cout << retrieveValue << endl; //This gives me exactly the 23.14 I wanted, perfect!

虽然这很好用,但我想了解那里到底发生了什么。 我正在将randomValue的地址转换为char*,并将该地址中的值写入文件?

我也很好奇,因为我需要为数组执行此操作,而我无法执行此操作:

int* myArray = new int[10];
//fill myArray values with random stuff

fileWriter.open("myFile.dat", ios::binary);
fileWriter.write((char*)&myArray, sizeof(myArray));
fileWriter.close();

据我了解,这只会在文件中写入第一个地址的值,而不是所有数组。因此,为了测试,我试图简单地将变量转换为 char* ,然后将其写入文件,然后转换回变量以查看是否正确检索了值,所以我同意:

int* intArray = new int[10];
for(int i = 0; i < 10; i++)
{
    cout << &intArray[i]; //the address of each number in my array
    cout << intArray[i]; //it's value
    cout << reinterpret_cast<char*>(&intArray[i]); //the char* value of each one     
}

但出于某种我不知道的原因,当我运行此代码时,我的计算机“发出哔哔声”。在数组期间,我还将这些保存到 char* 并尝试转换回 int,但我没有得到预期的结果,我得到了一些非常长的值。 比如:

float randomValue = 23.14f;

char* charValue = reinterpret_cast<char*>(&randomValue);
//charValue contains "B!¹A" plus a bunch of other (un-initiallized values?) characters, so I'm guessing the value is correct

//Now I'm here

我想把charValue转回randomValue,怎么办?

编辑:下面的答案中包含有价值的信息,但它们并不能解决我的(原始)问题。我正在测试这些类型的转换,因为我正在编写一个代码,我将选择一堆资源文件,例如 BMP、JPG、MP3,并将它们保存在一个按一些标准组织的 .DAT 文件中,我还没有完全想通了。

稍后,我将使用这个资源文件来读取这些内容并将其加载到我正在编码的程序(游戏)中。

我仍在考虑的标准,但我想知道是否可以这样做:

//In my ResourceFile.DAT
[4 bytes = objectID][3 bytes = objectType (WAV, MP3, JPG, BMP, etc)][4 bytes = objectLength][objectLength bytes = actual objectData]
//repeating this until end of file

然后在读取资源文件的代码中,我想做这样的事情(未经测试):

ifstream fileReader;
fileReader.open("myFile.DAT", ios::binary);
//file check stuff
while(!fileReader.eof())
{
    //Here I'll load
    int objectID = 0;
    fileReader((char*)&objectID, 4); //read 4 bytes to fill objectID

    char objectType[3];
    fileReader(&objectType, 3); //read the type so I know which parser use

    int objectLength = 0;
    fileReader((char*)&objectLength, 4); //get the length of the object data

    char* objectData = new char[objectLength];
    fileReader(objectData, objectLength); //fill objectData with the data

    //Here I'll use a parser to fill classes depending on the type etc, and move on to the next obj
}

目前我的代码正在处理原始文件(BMP、WAV 等)并将它们填充到类中,我想知道如何将这些文件中的数据保存到二进制数据文件中。 例如,我管理 BMP 数据的类有这样的:

class FileBMP
{
    public:
        int imageWidth;
        int imageHeight;
        int* imageData;
}

当我加载它时,我会调用:

void FileBMP::Load(int iwidth, int iheight)
{
    int imageTotalSize = iwidth * iheight * 4;
    imageData = new int[imageTotalSize]; //This will give me 4 times the amount of pixels in the image

    int cPixel = 0;
    while(cPixel < imageTotalSize)
    {
        imageData[cPixel] = 0;     //R value
        imageData[cPixel + 1] = 0; //G value
        imageData[cPixel + 2] = 0; //B value
        imageData[cPixel + 3] = 0; //A value
        cPixel += 4;
    }
} 

所以我有这个单维数组,其中包含每个像素 [RGBA] 格式的值,稍后我将使用它在屏幕上绘图。 我希望能够将这个数组保存为我在上面所说的计划的二进制数据格式,然后读取它并填充这个数组。

我认为这样的代码要求太高了,所以我想了解将这些值保存到二进制文件中然后读回以填充它需要知道什么。

抱歉,帖子太长了!

edit2:我通过第一次编辑解决了我的问题...感谢您提供宝贵的信息,我也知道我想要什么!

【问题讨论】:

    标签: c++ binary binaryfiles


    【解决方案1】:

    通过使用 & 运算符,您将获得指向变量内容的指针(将其视为内存地址)。

    float a = 123.45f;
    float* p = &a; // now p points to a, i.e. has the memory address to a's contents.
    char* c = (char*)&a; // c points to the same memory location, but the code says to treat the contents as char instead of float.
    

    当您为 write() 提供 (char*)&randomValue 时,您只需告诉“获取具有 char 数据的内存地址并从那里写入 sizeof(randomValue) 字符”。您不是在写地址值本身,而是来自该内存位置的内容(“原始二进制数据”)。

    cout << reinterpret_cast<char*>(&intArray[i]); //the char* value of each one 
    

    在这里,您应该提供 char* 类型的数据,以空字符(零)终止。但是,您改为提供浮点值的原始字节。您的程序可能会在此处崩溃,因为 cout 会输入字符,直到找到终止符 char ——它可能不会很快找到。

    float randomValue = 23.14f;
    char* charValue = reinterpret_cast<char*>(&randomValue);
    
    float back = *(float*)charValue;
    

    编辑:要保存二进制数据,您只需提供数据并 write() 即可。不要将

        int values[3] = { 5, 6, 7 };
    struct AnyData
    {
       float a;
       int b;
    } data;
    
    cout.write((char*)&values, sizeof(int) * 3); // the other two values follow the first one, you can write them all at once.
    cout.write((char*)&data, sizeof(data)); // you can also save structs that do not have pointers.
    

    如果您要编写结构,请查看#pragma pack 编译器指令。编译器会将变量对齐(使用填充)到某个大小(int),这意味着以下结构实际上可能需要 8 个字节:

    #pragma pack (push, 1)
    struct CouldBeLongerThanYouThink
    {
      char a;
      char b;
    };
    #pragma pack (pop)
    

    另外,不要自己写指针值(如果结构中有指针成员),因为一旦从文件读回,内存地址将不会指向任何有意义的数据。始终写入数据本身,而不是指针值。

    【讨论】:

    • 哦,这就是为什么我得到无效访问内存错误的原因......虽然第一块代码,它对未指定的字节没有任何问题吗? float 是 4 个字节,char 是 1 个字节,当我说将其视为 char* 时,其他 3 个字节会发生什么? (我不想在转换和保存到文件时丢失信息)
    • 当您告诉 write() 写入 4 个字符时,无论指针指向何处或数据实际是什么类型,它都会尝试这样做。 read() 也是如此。 C++真的不在乎你实际拥有多少字节或什么类型,char* 只是告诉指针指向的地方应该有一个 char。您也不应该相信 char 是单字节,即使几乎总是如此。一些读/写函数将 void* 输入和字节数作为参数(不像 fstream 方法,它需要字符)。
    【解决方案2】:

    发生的事情是您正在复制内部 将数据表示到文件中,然后将其复制回来 到内存中,只要程序执行 写作是用相同版本的编译器编译的, 使用相同的选项。否则,它可能会或可能不会 工作,取决于你无法控制的任何事情。

    我不清楚您要做什么,但格式如下 .jpg 和 .bmp 通常指定他们想要的格式 有不同的类型,你必须尊重这种格式。

    【讨论】:

      【解决方案3】:

      不清楚您真正想要做什么,所以我不能推荐一种解决您真正问题的方法。但是,如果运行该程序实际上会在您的程序中引起哔声或任何其他奇怪的行为,我不会感到惊讶。

      int* intArray = new int[10];
      for(int i = 0; i < 10; i++)
      {
          cout << reinterpret_cast<char*>(&intArray[i]);
      }
      

      上面new 返回的内存是未初始化的,但是你试图打印它,就好像它是一个空终止的字符串。未初始化的内存可能有铃铛字符(当打印到终端时会导致哔哔声)或任何其他值,包括它可能没有空终止,并且流中的插入运算符将超出缓冲区,直到找到 null 或您的程序在访问无效内存时崩溃。

      您的代码中还有其他不正确的假设,例如给定 int *p = new int[10]; 表达式 sizeof(p) 将是您架构中指针的大小,而不是整数大小的 10 倍。

      【讨论】:

        猜你喜欢
        • 2011-09-14
        • 2021-07-29
        • 1970-01-01
        • 2017-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多