Возникли проблемы с сериализацией двоичных данных с использованием ifstream и ofstream - PullRequest
2 голосов
/ 18 апреля 2011

Я пытаюсь сериализовать простую старую структуру данных, используя ifstream и ofstream, и я не смог заставить ее работать. Затем я попытался свести мою проблему к ультраосновной сериализации только char и int, и даже это не сработало. Очевидно, что я что-то упускаю на фундаментальном фундаментальном уровне.

Для базовой структуры:

struct SerializeTestStruct
{
    char mCharVal;
    unsigned int mIntVal;

    void Serialize(std::ofstream& ofs);
};

С функцией сериализации:

void SerializeTestStruct::Serialize(std::ofstream& ofs)
{
    bool isError = (false == ofs.good());
    if (false == isError)
    {
        ofs.write((char*)&mCharVal, sizeof(mCharVal));
        ofs.write((char*)&mIntVal, sizeof(mIntVal));
    }
}

Почему это не сработает со следующей короткой программой?

//ultra basic serialization test.
    SerializeTestStruct* testStruct = new SerializeTestStruct();
    testStruct->mCharVal = 'y';
    testStruct->mIntVal = 9;

    //write
    std::string testFileName = "test.bin";
    std::ofstream fileOut(testFileName.data());
    fileOut.open(testFileName.data(), std::ofstream::binary|std::ofstream::out);
    fileOut.clear();
    testStruct->Serialize(fileOut);

    fileOut.flush();
    fileOut.close();

    delete testStruct;

    //read
    char * memblock;
    std::ifstream fileIn (testFileName.data(), std::ifstream::in|std::ifstream::binary);
    if (fileIn.is_open())
    {
        // get length of file:
        fileIn.seekg (0, std::ifstream::end);
        int length = fileIn.tellg();
        fileIn.seekg (0, std::ifstream::beg);

        // allocate memory:
        memblock = new char [length];
        fileIn.read(memblock, length);
        fileIn.close();

        // read data as a block:
        SerializeTestStruct* testStruct2 = new(memblock) SerializeTestStruct();

        delete[] testStruct2;
    }

Когда я бегу по коду, я замечаю, что memblock имеет "y" вверху, так что, возможно, он работает, и это просто проблема с placement new в самом конце? После этого размещения я получаю SerializeTestStruct со значениями: 0, 0.

Ответы [ 5 ]

1 голос
/ 19 апреля 2011

Вот как должны читаться ваши вещи:

#include <fstream>
#include <string>
#include <stdexcept>

struct SerializeTestStruct
{
    char mCharVal;
    unsigned int mIntVal;

    void Serialize(::std::ostream &os);
    static SerializeTestStruct Deserialize(::std::istream &is);
};

void SerializeTestStruct::Serialize(std::ostream &os)
{
    if (os.good())
    {
        os.write((char*)&mCharVal, sizeof(mCharVal));
        os.write((char*)&mIntVal, sizeof(mIntVal));
    }
}

SerializeTestStruct SerializeTestStruct::Deserialize(std::istream &is)
{
        SerializeTestStruct retval;

    if (is.good())
    {
        is.read((char*)&retval.mCharVal, sizeof(retval.mCharVal));
        is.read((char*)&retval.mIntVal, sizeof(retval.mIntVal));
    }
    if (is.fail()) {
        throw ::std::runtime_error("failed to read full struct");
    }
    return retval;
}

int main(int argc, const char *argv[])
{
//ultra basic serialization test.

    // setup
    const ::std::string testFileName = "test.bin";

    // write
    {
        SerializeTestStruct testStruct;
        testStruct.mCharVal = 'y';
        testStruct.mIntVal = 9;

        ::std::ofstream fileOut(testFileName.c_str());
        fileOut.open(testFileName.c_str(),
                     std::ofstream::binary|std::ofstream::out);
        fileOut.clear();
        testStruct.Serialize(fileOut);
    }

    // read
    {
        ::std::ifstream fileIn (testFileName.c_str(),
                                std::ifstream::in|std::ifstream::binary);
        if (fileIn.is_open())
        {
            SerializeTestStruct testStruct =            \
                SerializeTestStruct::Deserialize(fileIn);

            ::std::cout << "testStruct.mCharVal == '" << testStruct.mCharVal
                        << "' && testStruct.mIntVal == " << testStruct.mIntVal
                        << '\n';
        }
    }
    return 0;
}

Проблемы стиля:

  • Не используйте new для создания вещей, если вы можете помочь.Выделенные в стек объекты обычно являются тем, что вы хотите, и ими значительно легче управлять, чем произвольными объектами времени жизни, которые вы выделяете из кучи.Если вы используете new, рассмотрите возможность использования какого-либо типа интеллектуального указателя, чтобы помочь вам управлять временем жизни.
  • Код сериализации и десериализации должен быть сопоставлен, чтобы их можно было изучить и изменить вместе.Это делает обслуживание такого кода намного проще.
  • Положитесь на C ++, чтобы очистить вас от деструкторов, вот для чего они.Это означает создание базовых блоков, содержащих части вашего кода, если область используемых переменных относительно ограничена.
  • Не используйте ненужные флаги.

Ошибки ...

  • Не используйте функцию data члена ::std::string.
  • Использование размещения new, и этот блок памяти - действительно плохая идея, потому что это невероятно сложно.И если вы использовали его, то вы не используете удаление массива так, как вы это делали.И, наконец, он все равно не будет работать по причине, объясненной позже.
  • Не используйте ofstream в типе, принятом вашей функцией Serialize, поскольку это производный класс, функции которого вам не нужны,Вы всегда должны использовать самый базовый класс в иерархии, который имеет необходимые вам функции, если только у вас нет особых причин не делать этого.Serialize отлично работает с функциями базового класса ostream, поэтому используйте этот тип вместо этого.
  • Расположение вашей структуры на диске и расположение в памяти не совпадают, так что ваша техника размещения - новая техникаобречен на провал.Как правило, если у вас есть функция serialize, вам нужна соответствующая функция deserialize.

Вот еще одно объяснение вашей проблемы с макетом памяти.Структура в памяти для Linux-бокса на базе x86_64 выглядит следующим образом:

+------------+-----------+
|Byte number | contents  |
+============+===========+
|          0 |     0x79  |
|            | (aka 'y') |
+------------+-----------+
|          1 |   padding |
+------------+-----------+
|          3 |   padding |
+------------+-----------+
|          4 |   padding |
+------------+-----------+
|          5 |         9 |
+------------+-----------+
|          6 |         0 |
+------------+-----------+
|          7 |         0 |
+------------+-----------+
|          8 |         0 |
+------------+-----------+

Содержимое раздела padding не определено, но обычно 0.Это не имеет значения, потому что это пространство никогда не используется и просто существует, так что доступ к следующему int лежит на эффективной 4-байтовой границе.

Размер вашей структуры на диске составляет 5 байтов,и полностью отсутствует разделы заполнения.Таким образом, это означает, что когда вы читаете его в память, он вообще не согласуется со структурой в памяти, и доступ к нему может вызвать какую-то ужасную проблему.

Первое правило, если вам нужноserialize функция, вам нужна deserialize функция.Второе правило: если вы точно не знаете, что делаете, не записывайте необработанную память в файл.Это будет хорошо работать во многих случаях, но есть важные случаи, в которых это не будет работать.И если вы не знаете, что работает и не работает, и когда это работает или не работает, вы в конечном итоге получите код, который, кажется, работает нормально в определенных тестовых ситуациях, но терпит неудачу, когда вы пытаетесь использовать его вреальная система.

Мой код все еще делает дамп памяти в файл.И это должно работать до тех пор, пока вы читаете результат обратно на той же самой архитектуре и платформе с кодом, скомпилированным с той же версией компилятора, что и при написании.Как только одна из этих переменных изменится, все ставки отключены.

1 голос
/ 18 апреля 2011

Проблема здесь:

SerializeTestStruct* testStruct2 = new(memblock) SerializeTestStruct();

Это создаст инициализированный значением объект типа SerializeTestStruct в ранее выделенной памяти.Он заполнит memblock нулями, поскольку инициализация значения равна инициализация нуля для POD-типов ( подробнее ).

Вот быстрое исправление для вашего кода:

SerializeTestStruct* testStruct2 = new SerializeTestStruct;
fileIn.read( (char*)&testStruct2->mCharVal, sizeof(testStruct2->mCharVal) );
fileIn.read( (char*)&testStruct2->mIntVal, sizeof(testStruct2->mIntVal) );
fileIn.close();
// do some with testStruct2
// ...
delete testStruct2;
1 голос
/ 18 апреля 2011
bool isError = (false == ofs.good());
if (false == isError)
{
    ofs.write((char*)&mCharVal, sizeof(mCharVal));
    ofs.write((char*)&mIntVal, sizeof(mIntVal));
}

изменить на

if ( ofs.good() )
{
    ofs.write((char*)&mCharVal, sizeof(mCharVal));
    ofs.write((char*)&mIntVal, sizeof(mIntVal));
}

Я бы сделал:

ostream & operator << ( ostream &os, const SerializeTestStruct &mystruct )
{
  if ( ofs.good() )
  {
    os.write((char*)&mystruct.mCharVal, sizeof(mCharVal));
    os.write((char*)&mystruct.mIntVal, sizeof(mIntVal));
  }
  return os;
}
0 голосов
/ 19 апреля 2011

На мой взгляд, вам нужно разрешить сериализацию в буфер, а не напрямую в поток. Запись в буфер позволяет записывать вложенные или унаследованные классы в память, тогда весь буфер может быть записан в поток. Запись битов и фрагментов в поток неэффективна.

Вот что я придумал, прежде чем перестал записывать двоичные данные в потоки:

struct Serialization_Interface
{
    //!  Returns size occupied on a stream.
    /*! Note:  size on the platform may be different.
     *  This method is used to allocate memory.
     */
    virtual size_t  size_on_stream(void) const = 0;

    //!  Stores the fields of the object to the given pointer.
    /*!  Pointer is incremented by the size on the stream.
     */
    virtual void    store_to_buffer(unsigned char *& p_buffer) const = 0;

    //!  Loads the object's fields from the buffer, advancing the pointer.
    virtual void    load_from_buffer(const unsigned char *& p_buffer) = 0;
};

struct Serialize_Test_Structure
  : Serialization_Interface
{
    char mCharVal;
    int  mIntVal;

    size_t  size_on_stream(void) const
    {
        return sizeof(mCharVal) + sizeof(mIntVal);
    }

    void  store_to_buffer(unsigned char *& p_buffer) const
    {
        *p_buffer++ = mCharVal;
        ((int&)(*p_buffer)) = mIntVal;
        p_buffer += sizeof(mIntVal);
        return;
    }

    void  load_from_buffer(const unsigned char *& p_buffer)
    {
         mCharVal = *p_buffer++;
         mIntVal = (const int&)(*p_buffer);
         p_buffer += sizeof(mIntVal);
         return;
    }
};


int main(void)
{
   struct Serialize_Test_Struct myStruct;
   myStruct.mCharVal = 'G';
   myStruct.mIntVal = 42;

   // Allocate a buffer:
   unsigned char * buffer = new unsigned char[](myStruct.size_on_stream());

   // Create output file.
   std::ofstream outfile("data.bin");

   // Does your design support this concept?
   unsigned char * p_buffer = buffer;
   myStruct.store_to_buffer(p_buffer);
   outfile.write((char *) buffer, myStruct.size_on_stream());

   outfile.close();
   return 0;
}

Я прекратил запись двоичных данных в потоки в пользу текстовых данных, потому что текстовые данные не должны беспокоиться о Endianess или о том, какой формат IEEE с плавающей запятой принимается принимающей платформой.

0 голосов
/ 18 апреля 2011

Я единственный, кто находит это совершенно непрозрачным:

bool isError = (false == ofs.good());
if (false == isError) {
    // stuff
}

почему бы и нет:

if ( ofs ) {
    // stuff
}
...