Урок 19. Паттерн 11. Сериализация и обмен данными
Важным элементом переноса программного решения на новую платформу является преемственность к существующим протоколам обмена данными. Необходимо обеспечить чтение существующих форматов проектов, осуществлять обмен данными между 32-битными и 64-битными процессами и так далее.
В основном, ошибки данного рода заключаются в сериализации memsize-типов и операциях обмена данными с их использованием:
1) size_t PixelsCount;
fread(&PixelsCount, sizeof(PixelsCount), 1, inFile);
2) __int32 value_1;
SSIZE_T value_2;
inputStream >> value_1 >> value_2;
В приведенных примерах имеются ошибки двух видов: использование типов непостоянной размерности в бинарных интерфейсах и игнорирование порядка байт.
Использование типов непостоянной размерности
Недопустимо использование типов, которые меняют свой размер в зависимости от среды разработки, в бинарных интерфейсах обмена данными. В языке Си++ все типы не имеют четкого размера и, следовательно, их все невозможно использовать для этих целей. Поэтому создатели средств разработки и сами программисты создают типы данных, имеющие строгий размер, такие как __int8, __int16, INT32, word64 и так далее.
Использование подобных типов обеспечивает переносимость данных между программами на различных платформах, хотя и требует дополнительных усилий. Два показанных примера написаны неаккуратно, что даст о себе знать при смене разрядности некоторых типов данных с 32 бит до 64 бит. Учитывая необходимость поддержки старых форматов данных, исправление может выглядеть следующим образом:
1) size_t PixelsCount;
__uint32 tmp;
fread(&tmp, sizeof(tmp), 1, inFile);
PixelsCount = static_cast<size_t>(tmp);
2) __int32 value_1;
__int32 value_2;
inputStream >> value_1 >> value_2;
Но приведенный вариант исправления может являться не лучшим. При переходе на 64-битную систему программа может обрабатывать большее количество данных, и использование в данных 32-битных типов может стать существенным препятствием. В таком случае, можно оставить старый код для совместимости со старым форматом данных, исправив некорректные типы. И реализовать новый бинарный формат данных уже с учетом допущенных ранее ошибок. Еще одним вариантом может стать отказ от бинарных форматов и переход на текстовый формат или другие форматы, предоставляемые различными библиотеками.
Игнорирование порядка байт (byte order)
Даже после внесения исправлений, касающихся размеров типа, вы можете столкнуться с несовместимостью бинарных форматов. Причина кроется в ином представлении данных. Наиболее часто это связано с другой последовательностью байт.
Порядок байт - метод записи байтов многобайтовых чисел (см. также рисунок 1). Порядок от младшего к старшему (англ. little-endian) - запись начинается с младшего и заканчивается старшим. Этот порядок записи принят в памяти персональных компьютеров с x86 и x86-64-процессорами. Порядок от старшего к младшему (англ. big-endian) - запись начинается со старшего и заканчивается младшим. Этот порядок является стандартным для протоколов TCP/IP. Поэтому, порядок байтов от старшего к младшему часто называют сетевым порядком байтов (англ. network byte order). Этот порядок байт используется процессорами Motorola 68000, SPARC.
Рисунок 1 - Порядок байт в 64-битном типе на little-endian и big-endian системах
Разрабатывая бинарный интерфейс или формат данных, следует помнить о последовательности байт. А если 64-битная система, на которую Вы переносите 32-битное приложение, имеет иную последовательность байт, то вы просто будете вынуждены учесть это в своем коде. Для преобразования между сетевым порядком байт (big-endian) и порядком байт (little-endian), можно использовать функции htonl(), htons(), bswap_64, и так далее.
Примечание. Во многих системах отсутствуют функции подобные bswap_64. А функция ntohl() позволяет перевертывать только 32-битные значения. Про вариант этой функции для 64-битных типов как-то забыли. Если вам нужно поменять порядок байт в 64-битной переменной, то на сайте stackoverflow.com имеется обсуждение темы "64 bit ntohl() in C++ ?" где приведено сразу несколько примеров реализации такой функции.
Диагностика
К сожалению PVS-Studio не реализует диагностику данного паттерна 64-битных ошибок, так как этот процесс не удается формализовать (составить диагностическое правило). Можно только порекомендовать внимательно просмотреть все участки кода, отвечающие за чтения и запись данных, а также за передачу их в другие процессы, например посредством технологии COM.
Мы также будем рады, если кто-то из читателей предложит практические шаги, как хотя бы частично выявлять ошибки описанного типа.
Авторы курса: Андрей Карпов (karpov@viva64.com), Евгений Рыжков (evg@viva64.com).
Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++" является ООО "Системы программной верификации". Компания занимается разработкой программного обеспечения в области анализа исходного кода программ. Сайт компании: http://www.viva64.com.