Урок 16. Паттерн 8. Memsize-типы в объединениях


Особенностью объединения (union) является то, что для всех элементов (членов) объединения выделяется одна и та же область памяти, то есть они перекрываются. Хотя доступ к этой области памяти возможен с использованием любого из элементов, элемент для этой цели должен выбираться так, чтобы полученный результат был осмысленным.

Следует внимательно отнестись к объединениям, имеющим в своем составе указатели и другие члены типа memsize.

Когда возникает необходимость работать с указателем как с целым числом, иногда удобно воспользоваться объединением, как показано в примере, и работать с числовым представлением типа без использования явных приведений:

union PtrNumUnion {
  char *m_p;
  unsigned m_n;
} u;
u.m_p = str;
u.m_n += delta;

Данный код корректен на 32-битных системах и некорректен на 64-битных. Изменяя член m_n на 64-битной системе, мы работаем только с частью указателя m_p (смотри рисунок 1).

Рисунок 1 - Формат объединения на 32-битной и 64-битной системе

Рисунок 1 - Формат объединения на 32-битной и 64-битной системе

Следует использовать тип, который будет соответствовать размеру указателя:

union PtrNumUnion {
  char *m_p;
  size_t m_n; //type fixed
} u;

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

union SizetToBytesUnion {
  size_t value;
  struct {
    unsigned char b0, b1, b2, b3;
  } bytes;
} u;
   
SizetToBytesUnion u;
u.value = value;
size_t zeroBitsN = TranslateTable[u.bytes.b0] +
                   TranslateTable[u.bytes.b1] +
                   TranslateTable[u.bytes.b2] +
                   TranslateTable[u.bytes.b3];

Здесь допущена принципиальная алгоритмическая ошибка, заключающаяся в предположении, что тип size_t состоит из 4 байт. Возможность автоматического поиска алгоритмических ошибок пока вряд ли возможна, но мы можем осуществить поиск всех объединений и проверить наличие в них memsize-типов. Найдя такое объединение, мы можем обнаружить алгоритмическую ошибку и переписать код следующим образом.

union SizetToBytesUnion {
  size_t value;
  unsigned char bytes[sizeof(value)];
} u;
   
SizetToBytesUnion u;
u.value = value;
size_t zeroBitsN = 0;
for (size_t i = 0; i != sizeof(bytes); ++i)
  zeroBitsN += TranslateTable[bytes[i]]; 

Диагностика

Инструмент PVS-Studio позволяет быстро найти и просмотреть в коде программы все объединения, содержащие memsize типы. Анализатор выдает диагностическое сообщение V117 для структур, на которые следует обратить внимание программисту при переносе кода на 64-битную систему.

Авторы курса: Андрей Карпов (karpov@viva64.com), Евгений Рыжков (evg@viva64.com).

Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++" является ООО "Системы программной верификации". Компания занимается разработкой программного обеспечения в области анализа исходного кода программ. Сайт компании: http://www.viva64.com.



Найдите ошибки в своем C, C++, C# и Java коде

Предлагаем попробовать проверить код вашего проекта с помощью анализатора кода PVS-Studio. Одна найденная в нём ошибка скажет вам о пользе методологии статического анализа кода больше, чем десяток статей.

goto PVS-Studio;


Найденные ошибки

Проверено проектов
346
Собрано ошибок
13 188

А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++, C#
и Java

goto PVS-Studio;