Изменения выравнивания типов и последствия

Andrey Karpov (20 пост(а)) 02.10.2009 13:14

При переносе программного обеспечения одной из забот, которая ложится на плечи разработчика является изменение размерности типов и правил их выравнивания. Не так давно мы поддержали в анализаторе Viva64 диагностическое правило, позволяющее обнаружить структуры данных, неэффективно использующие память на 64-битных системах. Но в данном направлении еще стоит продолжать исследования и я внимательно просматриваю сообщения в форумах по этому поводу.

В этот раз мое внимание привлекло сообщение в форуме RSDN следующего содержания:

Столкнулся сегодня с одной проблемой в Linux. Есть структура данных, состоящая из нескольких полей: 64-битный double, потом 8 unsigned char и один 32-битный int. Итого получается 20 байт (8 + 8*1 + 4). Под 32-битными системами sizeof равен 20 и всё работает нормально. А под 64-битным Linux'ом sizeof возвращает 24. Т.е. идёт выравнивание по границе 64 бит.

После чего идут рассуждения о совместимости данных и просьба совета, как упаковать данные в структуре. Но не это сейчас интересно. Интереснее то, что здесь наблюдается новый тип ошибки, который может возникнуть при портировании приложений на 64-битную систему.

Когда меняются размеры полей в структуре и из-за этого меняется сам размер структуры это понятно и привычно. Но здесь другой случай. Размер полей остался прежний, но из-за иных правил выравнивания размер структуры все равно изменится. Такое поведение может привести к разнообразным ошибкам, например в несовместимости форматов сохраняемых данных.

Viva64 пока не поддерживает Linux системы, и я решил выяснить может ли возникнуть данный тип ошибок и в Windows системах. Для этого я взял из статьи «C++ data alignment and portability» пример кода, выводящий на печать размер типов и их выравнивание. Немного модифицировал его для Visual Studio, после чего получилась вот такая программа:

#include <iostream>
using namespace std;

template <typename T>
void print (char const* name)
{
  cerr << name
       << " sizeof = " << sizeof (T)
       << " alignof = " << __alignof (T)
       << endl;
}

int _tmain(int, _TCHAR *[])
{
  print<bool>        ("bool          ");
  print<wchar_t>     ("wchar_t       ");
  print<short>       ("short int     ");
  print<int>         ("int           ");
  print<long>        ("long int      ");
  print<long long>   ("long long int ");
  print<float>       ("float         ");
  print<double>      ("double        ");
  print<long double> ("long double   ");
  print<void*>       ("void*         ");
}

Полученные данные, я совместил с данными из статьи «C++ data alignment and portability» для GNU/Linux систем и привожу их в таблице.

Давайте изучим эту таблицу. Обратите внимание на выделенные ячейки, относящиеся к типам long long int и double. Эти типы не меняют свои размеры в зависимости от разрядности архитектуры. На 32-битной и на 64-битных системах они имеют размер 8 байт. Но выравнивание для 32-битных и 64-битных систем различно. Это как раз и может привести к изменению размера структуры. Когда мы будем реализовывать Viva64 под Linux, мы обязательно учтем возможность возникновения связанных с этим потенциальных ошибок.

В Windows системах подобных потенциальных проблем с изменением выравнивания не наблюдается. Обратите внимание, что выравнивание всех типов остается неизменным или меняется вместе с изменением размера типа. Хорошо. У Windows разработчиков одной потенциальной проблемой меньше.

Категории: Разработка софта

Комментарии (14)

02.10.2009 05:32

Dmitriy Vyukov
Всего баллов:
25,462
Статусных баллов:
25,462
черный пояс
Не понятно, а в чём собственно могут быть проблемы. Выравнивание есть в общем случае прозрачная для программиста вещь. Т.е. компилятор может выбрать совершенно произвольные выравнивания для типов, и менять их при каждой смене версии, или даже выбирать различные выравнивания для членов каждого класса. И это есть нормально, покуда компилятор работает с выравниваниями консистентно, т.е. нет такого, что компилятор при компиляции одной единицы трансляции выбрал для членов класса одно выравнивание, а при компиляции другой - другое (но это уже будет банальная ошибка в компиляторе).

02.10.2009 05:44

Dmitriy Vyukov
Всего баллов:
25,462
Статусных баллов:
25,462
черный пояс
Кстати, расхождение с double лучится флагом -malign-double, который используется повсеместно. Проблема с long long будет лечится флагом -malign-long-long (пока имеется только в виде патча).
А для студии выравнивания контролируются флагом /ZpN.
02.10.2009 07:00

Andrey Karpov
Всего баллов:
5,753
Статусных баллов:
5,253
коричневый пояс
Проблем как бы нет. А ошибки есть (например в старом коде). Исправить их легко. А вот найти далеко не всегда. Чтобы написать ключ "-malign-long-long" в начале следует узнать, что его надо написать, чтобы избежать ошибки. Не писать же его просто так на всякий случай. :-)

Здесь ошибка сохранения и восстановления данных. Пусть в программе есть структуры, содержащие поля типов double и int. Программист надеется, что размер этих данных не меняется и следовательно размер структуры данных остается прежним. Размер этих данных действительно остается одинаковым на 32-битной и 64-битной системе. А вот размер структуры из-за выравнивания изменится. И механизм сохранения/восстановления начнет работать неверно.

Конечно ясно, полагаться на такие вещи нельзя. Понятно, что нужно аккуратнее относится к механизму сохранения и восстановления данных. Следует использовать специальные библиотеки, писать специальный код и так далее. Но так делают в идеальном мире. А в реальных программах, имеется то, что имеется. И цель, описывая такие проблемы помочь людям как можно быстрее обнаружить дефект.
02.10.2009 09:37

mt2
Всего баллов:
11,649
Статусных баллов:
11,149
коричневый пояс
Мне приходилось сталкиваться с библиотеками, где применялись трюки, в которых исходили из определенного sizeof. Очень неприятный случай :(
02.10.2009 11:39

ksili
Всего баллов:
4,113
Статусных баллов:
3,613
коричневый пояс
> Не понятно, а в чём собственно могут быть проблемы.
Ну наверно, ошибки могут быть при взаимодействии систем с разной разрядностью. Например сервер на 64, а клиент на 64 бита. Чтобы передать какую-нибудь структуру серверу, клиент её может сериализовать, а сервер у себя десериализует. Из-за разных размеров структур теоретически могут поля и поехать.
03.10.2009 02:53

Dmitriy Vyukov
Всего баллов:
25,462
Статусных баллов:
25,462
черный пояс
А, понятно. Спасибо.
Но теперь не понятно, на какой основе будут выдаваться варнинги. Для любого класса, в котором есть члены типа wchar_t, long, double? Тут нужны какие-то дополнительные ограничения, иначе ИМХО такой варнинг будет практически бесполезным...
04.10.2009 23:35

Andrey Karpov
Всего баллов:
5,753
Статусных баллов:
5,253
коричневый пояс
Мы пока не думали над этим вопросом. Возможно, даже не удастся составить удачное правило, дающее приемлемое количество ложных срабатываний. Но, по крайней мере, можно отсечь предупреждения для тех структур, которые хотя и содержат double или long long, но не меняют свой размер.

Например, размер этой структуры не изменится и предупреждение выдавать нет смысла:

struct MyPoint {
double x;
double y;
double z;
};
05.10.2009 02:46

Dmitriy Vyukov
Всего баллов:
25,462
Статусных баллов:
25,462
черный пояс
Андрей, Вы наверное имели в виду раскладку объектов, а не размер. Т.к. размер-то может и не измениться, а вот раскладка изменится, просто паддинг будет в других местах.
Кстати, а ваши инструменты учитывают обработку препроцессором? Т.к. если речь идёт о портируемости, то в мире С/C++ препроцессор - одно из основных средств (в плане определения всяких uint64_t и т.д.).
05.10.2009 05:14

Andrey Karpov
Всего баллов:
5,753
Статусных баллов:
5,253
коричневый пояс
Вы правы Дмитрий. Не должна измениться раскладка объектов. Спасибо за внимательность. :)

По поводу препроцессора. Да, учитывается. При работе мы используем как препроцессированные файлы. Хотя встроенного препроцессора у нас нет, мы используем препроцессор Visual C++.
05.10.2009 06:28

Dmitriy Vyukov
Всего баллов:
25,462
Статусных баллов:
25,462
черный пояс
Я имел в виду не совсем это. Допустим есть что-то типа такого:
#if MSVC
#  if ARCH32
      typedef long my_type_t;
#  else
      typedef int my_type_t;
#  endif
#else
#  if ARCH32
      typedef long long my_type_t;
#  else
      typedef long my_type_t;
#  endif
#endif

Разговор идёт о портировании с одной платформы на другую. Но вместе с портированием скорее всего изменится и ряд определений, т.к. будут определены другие макросы.
05.10.2009 06:53

Andrey Karpov
Всего баллов:
5,753
Статусных баллов:
5,253
коричневый пояс
Прошу привести пример. Пока ход мысли мне не понятен.
05.10.2009 10:34

Dmitriy Vyukov
Всего баллов:
25,462
Статусных баллов:
25,462
черный пояс
Я имею в виду следующее. Всё это о портировании. Т.е. вы хотите выдавать варнинги, что при портировании допустим с Windows на Linux такой-то структуры могут быть проблемы. НО на другой платформе эта структура может быть определена ПО-ДРУГОМУ. Из-за препроцессора.
Допустим есть структура:
struct Foo
{
...
my_type_t m;
...
};
На текущей платформе (виндовс) my_type_t есть int. Допустим при портировании на линукс, это вызвает проблемы. Но на линуксе my_type_t уже будет не int, а long. И в таком случае проблемы уже нет, программист всё сделал правильно.
Конечно, это всё можно и не учитывать, но тогда кол-во ложных срабатываний может быть слишком высоким. Что бы его снизить необходимо учитывать, что при смене платформы определения некоторых типов могут изменяться.
06.10.2009 00:26

Andrey Karpov
Всего баллов:
5,753
Статусных баллов:
5,253
коричневый пояс
"Т.е. вы хотите выдавать варнинги, что при портировании допустим с Windows на Linux такой-то структуры могут быть проблемы."

Нет. Такая задача требует отдельного рассмотрения. Речь в посте шла о том, что иногда при переходе с 32-битной на 64-битную систему изменяется выравнивание данных в структурах, хотя размеры типов остаются прежними. Изменение правил выравнивания приводит к увеличению размера структуры, что потенциально может привести к ошибке.

Данный вопрос бы изучен и был сделан вывод, что данная проблема не актуальна для Windows-приложений, для которых мы разрабатываем анализатор Viva64 (входящий сейчас в состав PVS-Studio). Вопрос как подобные ошибки можно диагностировать на Linux-системах или при миграции приложений с Windows на Linux мы пока не изучали.

06.10.2009 03:05

Dmitriy Vyukov
Всего баллов:
25,462
Статусных баллов:
25,462
черный пояс
Понятно. Спасибо.

Обратная ссылка (0)


Оставить комментарий  

To obtain technical support, please go to Software Support.
Имя (обязательно)*

Электронная почта (обязательно; не будет отображено на этой странице)*

Ваш URL-адрес (необязательно)


Комментарий*