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

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

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

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

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

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

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

Ответ 1

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

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

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


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

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

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

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

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

Ответ 2

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

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

Я много работаю со встроенными системами, и у меня есть функции в моей собственной библиотеке, которые я всегда использую при создании или анализе сетевых пакетов (или любых других I/O: дисков, 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, если хотите.

Ответ 4

Вы можете проверить MessagePack или Binn.

Но для C интерфейс Binn проще в использовании. Примеры:

Создание списка:

  binn *list;

  // create a new list
  list = binn_list();

  // add values to it
  binn_list_add_int32(list, 123);
  binn_list_add_double(list, 2.55);
  binn_list_add_str(list, "testing");

  // send over the network or save to a file...
  send(sock, binn_ptr(list), binn_size(list));

  // release the buffer
  binn_free(list);

Создание объекта:

  binn *obj;

  // create a new object
  obj = binn_object();

  // add values to it
  binn_object_set_int32(obj, "id", 123);
  binn_object_set_str(obj, "name", "John");
  binn_object_set_double(obj, "total", 2.55);

  // send over the network or save to a file...
  send(sock, binn_ptr(obj), binn_size(obj));

  // release the buffer
  binn_free(obj);