Переносимые примитивы сериализации 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
, если хотите.
Ответ 3
См. xdr библиотека и стандарты XDR RFC-1014 RFC-4506
Ответ 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);