Переносимые двоичные примитивы сериализации

Насколько я знаю, библиотека C не помогает в сериализации числовых значений в нетекстовый поток байтов. Поправьте меня если я ошибаюсь.

Самый стандартный используемый инструмент htonl и др. из POSIX. Эти функции имеют недостатки:

  • 64-битная поддержка отсутствует.
  • Нет поддержки с плавающей точкой.
  • Нет версий для подписанных типов. При десериализации преобразование без знака в подпись полагается на целочисленное переполнение со знаком, которое является UB.
  • Их имена не указывают размер типа данных.
  • Они зависят от 8-битных байтов и наличия точного размера uint_ N _t.
  • Типы ввода совпадают с типами вывода вместо ссылки на поток байтов.
    • Это требует, чтобы пользователь выполнил указатель типа, который, возможно, небезопасен при выравнивании.
    • Выполнив этот тип, пользователь, скорее всего, попытается преобразовать и вывести структуру в своей собственной структуре памяти, что является плохой практикой, которая приводит к непредвиденным ошибкам.

Интерфейс для сериализации произвольного размера char 8-битные стандартные байты попадают между стандартом C, который на самом деле не признает 8-битные байты, и какие бы стандарты (ITU?) не устанавливали октет в качестве основной единицы передачи. Но старые стандарты не пересматриваются.

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

Будет ли такое расширение полезным или беспокоиться о машинах, не являющихся комплиментами не-двух, просто так бессмысленно?

3 ответа

Решение

Я никогда не использовал их, но я думаю, что буфер протокола Google удовлетворяет вашим требованиям.

  • Поддерживаются64-битные типы, типы со знаком / без знака и типы с плавающей запятой.
  • Созданный API безопасен для типов
  • Сериализация может быть сделана в / из потоков

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


С их веб-страницы:

Что такое буферные протоколы?

Буферы протокола - это независимый от языка, платформенно-независимый, расширяемый механизм Google для сериализации структурированных данных - представьте XML, но меньше, быстрее и проще. Вы определяете, как вы хотите, чтобы ваши данные были структурированы один раз, затем вы можете использовать специальный сгенерированный исходный код, чтобы легко записывать и считывать ваши структурированные данные в различные потоки данных и из них, используя различные языки - Java, C++ или Python.

В чистом C нет официальной реализации (только C++), но есть два C-порта, которые могут соответствовать вашим потребностям:

Я не знаю, как они живут при наличии не 8-битных байтов, но это должно быть относительно легко узнать.

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

Многие процессоры не способны (эффективно) получать доступ к многобайтовым целым числам, которые не хранятся в ячейке памяти, адрес которой не кратен размеру целого числа в байтах. Это причина того, что никогда не использовать структурные оверлеи для (де) сериализации сетевых пакетов. Я не уверен, что это то, что вы подразумеваете под "преобразованием на месте".

Я много работаю со встроенными системами, и у меня есть функции в моей собственной библиотеке, которые я всегда использую при генерации или разборе сетевых пакетов (или любых других операций ввода-вывода: диск, RS232 и т. Д.):

/* Serialize an integer into a little or big endian byte buffer, resp. */
void SerializeLeInt(uint64_t value, uint8_t *buffer, size_t nrBytes);
void SerializeBeInt(uint64_t value, uint8_t *buffer, size_t nrBytes);

/* Deserialize an integer from a little or big endian byte buffer, resp. */
uint64_t DeserializeLeInt(const uint8_t *buffer, size_t nrBytes);
uint64_t DeserializeBeInt(const uint8_t *buffer, size_t nrBytes);

Наряду с этими функциями существует несколько макросов, таких как:

#define SerializeBeInt16(value, buffer)     SerializeBeInt(value, buffer, sizeof(int16_t))
#define SerializeBeUint16(value, buffer)    SerializeBeInt(value, buffer, sizeof(uint16_t))
#define DeserializeBeInt16(buffer)          DeserializeBeType(buffer, int16_t)
#define DeserializeBeUint16(buffer)         DeserializeBeType(buffer, uint16_t)

Функции (de) сериализации читают или записывают значения побайтно, поэтому проблем с выравниванием не возникнет. Вам не нужно беспокоиться о подписанности. Во-первых, все системы в наши дни используют дополнение 2s (возможно, кроме нескольких АЦП, но тогда вы не будете использовать эти функции). Однако это должно даже работать в системе, использующей дополнение 1s, потому что (насколько я знаю) знаковое целое число преобразуется в дополнение к 2s при приведении к unsigned (и функции принимают / возвращают целые числа без знака).

Еще один аргумент в пользу вас - они зависят от 8-битных байтов и наличия точного размера uint_N_t, Это также имеет значение для моих функций, но, по моему мнению, это не проблема (эти типы всегда определяются для систем и их компиляторов, с которыми я работаю). Вы можете настроить прототипы функций для использования unsigned char вместо uint8_t и что-то вроде long long или же uint_least64_t вместо uint64_t если хочешь.

См. Библиотеку xdr и стандарты XDR RFC-1014 RFC-4506

Другие вопросы по тегам