Фактический общий размер членов структуры - PullRequest
3 голосов
/ 27 октября 2009

Я должен записать массив данных структуры на жесткий диск:

<code>
struct Data {
  char cmember;
  /* padding bytes */
  int  imember;  
};

AFAIK, большинство компиляторов добавят несколько байтов заполнения между членами cmember и imember членов Data, но я хочу сохранить в файл только актуальные данные (без отступов).
У меня есть следующий код для сохранения массива данных (в буфере вместо файла для упрощения):

<code>
bool saveData(Data* data, int dataLen, char* targetBuff, int buffLen)
{
  int actualLen = sizeof(char) + sizeof(int); // this code force us to know internal
                                              // representation of Data structure
  int actualTotalLen = dataLen * actualLen; 
  if(actualTotalLen > buffLen) {
    return false;
  }

  for(int i = 0; i &lt dataLen; i++) {
    memcpy(targetBuff, &data[i].cmember, sizeof(char));
    targetBuff += sizeof(char);
    memcpy(targetBuff, &data[i].imember, sizeof(int));
    targetBuff += sizeof(int);
  }
  return true;
}

Как видите, я вычисляю фактический размер структуры данных с помощью кода: int actualLen = sizeof(char) + sizeof(int). Есть ли альтернатива этому? (что-то вроде int actualLen = actualSizeof(Data))

P.S. это синтетический пример, но я думаю, вы понимаете идею моего вопроса ...

Ответы [ 12 ]

8 голосов
/ 27 октября 2009

Просто сохраняйте каждого члена структуры по одному. Если вы перегружаете <<, чтобы записать переменную в файл, вы можете получить </p>

myfile << mystruct.member1 << mystruct.member2;

Тогда вы могли бы даже перегружать <<, чтобы взять всю структуру, и сделать это внутри оператора структуры <<, так что в итоге вы получите: </p>

myfile << mystruct;

В результате код сохранения выглядит так:

myfile << count;
for (int i = 0; i < count; ++i)
    myFile << data[i];

IMO, все, что возится с адресами памяти и memcpy, - слишком большая головная боль, когда вы можете сделать это таким образом. Эта общая техника называется сериализацией - попробуйте Google больше, это хорошо развитая область.

3 голосов
/ 27 октября 2009

Вы должны будете упаковать свою структуру.

Способ сделать это зависит от используемого вами компилятора.

Для визуального c ++:

#pragma pack(push)
#pragma pack(1)

struct PackedStruct {
    /* members */
};

#pragma pack(pop)

Это скажет компилятору не дополнять элементы в структуре и восстановить параметр pack к его начальному значению. Имейте в виду, что это повлияет на производительность. Если эта структура используется в критическом коде, вы можете скопировать распакованную структуру в упакованную структуру.

Кроме того, не поддавайтесь искушениям использовать параметр командной строки, который полностью отключает заполнение, это сильно повлияет на производительность.

2 голосов
/ 27 октября 2009

Я бы сказал, что вы на самом деле ищете сериализацию.

Существует ряд структур для сериализации, но я лично предпочитаю Буферы протокола Google , а не Boost.Serialization и другие подходы.

Протоколные буферы имеют версионный и бинарный / читабельный вывод.

Если вас беспокоит размер, у вас всегда есть возможность сжатия данных. Есть молниеносный алгоритм сжатия, такой как LZW , который предлагает хорошее соотношение скорости / сжатия, например.

2 голосов
/ 27 октября 2009

Нет простого решения этой проблемы. Обычно вы можете создавать отдельные структуры и указывать компилятору плотно их упаковать, например:

/* GNU has attributes */
struct PackedData {
    char cmember;
    int  imember;
} __attribute__((packed));

или

/* MSVC has headers and #pragmas */
#include <pshpack1.h>
struct PackedData {
    char cmember;
    int  imember;
};
#include <poppack.h>

Затем вам нужно написать код, который преобразует ваши неупакованные структуры в упакованные структуры и наоборот. Если вы используете C ++, вы можете создавать вспомогательные функции шаблона, которые основаны на типе структуры, а затем специализировать их:

template <typename T>
std::ostream& encode_to_stream(std::ostream& os, T const& object) {
    return os.write((char const*)&object, sizeof(object));
}

template <typename T>
std::istream& decode_from_stream(std::istream& is, T& object) {
    return is.read((char*)&object, sizeof(object));
}

template<>
std::ostream& encode_to_stream<Data>(std::ostream& os, Data const& object) {
    encode_to_stream<char>(os, object.cmember);
    encode_to_stream<int>(os, object.imember);
    return os;
}
template <>
std::istream& decode_from_stream<Data>(std::istream& is, Data& object) {
    decode_from_stream<char>(is, object.cmember);
    decode_from_stream<int>(is, object.imember);
    return is;
}

Бонус заключается в том, что по умолчанию будут считываться и записываться объекты POD, включая отступы. Вы можете специализироваться по мере необходимости, чтобы оптимизировать хранение. Тем не менее, вы, вероятно, захотите рассмотреть также вопросы порядка байтов, управления версиями и других бинарных хранилищ. Было бы разумно просто написать архивный класс, который обернет ваше хранилище и предоставит методы для сериализации и десериализации примитивов, а затем метод с открытым концом, который вы можете специализировать по мере необходимости:

class Archive {
protected:
    typedef unsigned char byte;
    void writeBytes(byte const* byte_ptr, std::size_t byte_size) {
        m_fstream.write((char const*)byte_ptr, byte_size);
    }

public:
    template <typename T>
    void writePOD(T const& pod) {
        writeBytes((byte const*)&pod, sizeof(pod));
    }

    // Users are required to specialize this to use it.  If it is used
    // for a type that it is not specialized for, a link error will occur.
    template <typename T> void serializeObject(T const& obj);
 };

 template<>
 void Archive::serializeObject<Data>(Data const& obj) {
     writePOD(cmember);
     writePOD(imember);
 }

Это подход, к которому я всегда прибегал после множества пертурбаций между ними. Он легко расширяется, не требуя наследования, и дает вам гибкость, позволяющую при необходимости изменять базовый формат хранения данных. Вы даже можете специализировать writePOD, чтобы делать разные вещи для разных базовых типов данных, например, гарантировать, что многобайтовые целые числа записываются в сетевом порядке или еще много чего.

1 голос
/ 27 октября 2009

Вы сказали @Coincoin, что не можете упаковать. Если вам просто нужен размер по какой-то причине, вот грязное решение

#define STRUCT_ELEMENTS  char cmember;/* padding bytes */ int  imember; 
typedef struct 
{
    STRUCT_ELEMENTS 
}paddedData;

#pragma pack(push)
#pragma pack(1)

typedef struct 
{
    STRUCT_ELEMENTS 
}packedData;
#pragma pop

теперь у вас есть размер обоих;

sizeof(packedData);
sizeof(paddedData);

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

1 голос
/ 27 октября 2009

Не знаю, поможет ли это вам, но я имею обыкновение упорядочивать членов структур, которые я намерен записывать в файлы (или отправлять по сетям), чтобы у них было как можно меньше заполнения. Это сделано для того, чтобы сначала поместить членов с самыми широкими типами данных и самым строгим выравниванием:

& бык; указатели первая
& Бык; double
& Бык; long long
& Бык; long
& Бык; float
& Бык; int
& Бык; short
& Бык; char
& Бык; битовые поля последняя

Любое дополнение, добавленное компилятором, будет добавлено в конец данных структуры.

Другими словами, вы могли бы упростить вашу проблему, устранив заполнение (если возможно), переупорядочив элементы структуры:

struct Data
{
    int     imember;
    char    cmember;
    /* padding bytes here */
};

Очевидно, что это не решит вашу проблему, если вы не сможете переупорядочить элементы структуры (потому что он используется сторонним API или вам нужны начальные члены для определенных типов данных).

1 голос
/ 27 октября 2009

Если вы не хотите использовать Pragma Pack, попробуйте вручную изменить порядок переменных, как

struct Data {
  int  imember;
  char cmember;

};
1 голос
/ 27 октября 2009

IIUC, вы пытаетесь скопировать значения элементов структуры, а не структуру в целом, и сохранить ее на диске. Ваш подход выглядит хорошо для меня. Я не согласен с теми, кто предлагает #pragma pack - так как они помогут вам получить упакованную структуру во время выполнения.

Несколько заметок:

  • sizeof (char) == 1, всегда по определению

  • использовать макрос offsetof()

  • не пытайтесь создавать экземпляр объекта Data непосредственно из этого targetBuff (то есть путем приведения) - это когда вы сталкиваетесь с проблемами выравнивания и отключением. Вместо этого скопируйте элементы, как вы делали во время записи в буфер, и у вас не должно возникнуть проблем
1 голос
/ 27 октября 2009

Как видите, я вычисляю фактический размер структуры данных с помощью кода: int actualLen = sizeof (char) + sizeof (int). Есть ли альтернатива этому?

Нет, не в стандартном C ++.

Ваш компилятор может предоставить специфичную для компилятора опцию. Упакованные структуры, как показано Graeme и Coincoin , могут подойти.

1 голос
/ 27 октября 2009

Посмотрите макрос #pragma pack для вашего компилятора. Некоторые компиляторы используют #pragma options align=packed или что-то подобное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...