Такое сообщение компилятор IAR 4.41A (программа микроконтроллера ARM7 в режиме Thumb) выдавал на код модуля ff.c библиотеки FatFs-07c [1]. Пример строки, на которую выдается предупреждение Pa039 (параметр, который вызывает предупреждение, выделен в примере жирным шрифтом):
res = auto_mount(&path, &dj.fs, 0);
Здесь компилятор предупреждает о том, что используется адрес не выровненного на 4 байта члена структуры. В данном случае это структура DIR dj, в которой отключено выравнивание полей директивой #pragma pack(1):
/* Объект структуры каталога */
//typedef __packed struct _DIR_
#pragma pack(1)
typedef struct _DIR_
{
FATFS* fs; /* Указатель на владельца объекта файловой системы */
WORD id; /* Идентификатор монтирования владельца файловой системы (mount ID) */
WORD index; /* Индекс текущей операции чтения/записи */
DWORD sclust; /* Начало таблицы кластеров (0: статическая таблица) */
DWORD clust; /* Текущий кластер */
DWORD sect; /* Текущий сектор */
BYTE* dir; /* Указатель на текущую запись SFN (короткое имя файла) в win[] */
BYTE* fn; /* Указатель на SFN (in/out) {file[8],ext[3],status[1]} */
#if _USE_LFN
WCHAR* lfn; /* Указатель на рабочий буфер LFN (длинное имя файла) */
WORD lfn_idx; /* Последний подошедший индекс LFN (0xFFFF: нет LFN) */
#endif
} DIR;
#pragma pack()
Какие может иметь последствия предупреждение Pa039, и как от него избавиться? Обычно на 32-разрядных процессорах ARM7 это означает только то, что для не выровненных структур может использоваться не такой эффективный код, какой мог бы быть сгенерирован для структур, у которых все поля выровнены на адрес, нацело делящийся на 4. Для конкретного случая с FatFS можно совсем убрать директиву выравнивания #pack(1), что никак не отразится на работоспособности кода.
[Что такое выравнивание структуры?]
Все современные CPU ожидают обработки фундаментальных типов, таких как int, long и float, которые сохранены в памяти по адресам, кратным длине этих фундаментальных типов (так называемые выровненные адреса). CPU оптимизированы для доступа к выравненным адресам адресного пространства. Некоторые CPU (такие как Intel x86) допускают невыровненный доступ к памяти, но ценой снижения быстродействия. Некоторые CPU перехватывают невыровненный доступ как исключение (прерывание ошибки, которое обычно передается операционной системе), чтобы такие события могли быть игнорированы, симулированы выполнением специальных подпрограмм или о них передавались сообщения об ошибке. Другие CPU (как ранние процессоры ARM) использовали невыровненный адрес, что означало выполнение специальных операций при загрузке данных их памяти (load) или сохранении данных в память (store).
Когда компилятор C обрабатывает декларацию структуры, он может добавить дополнительные байты между полями структуры, чтобы гарантировать, что начальные адреса всех полей получили правильно выровненные адреса в памяти. Это также гарантирует, что все экземпляры этой структуры, когда они определяются в программе, будут находиться в памяти по выровненным адресам. Это также добавит дополнительные байты в конце структуры, чтобы гарантировать, что все массивы, входящие в структуру, были правильно выравнены. Функция "malloc" и тому подобные библиотечные функции для динамического выделения памяти из кучи всегда возвратят указатели на память, значение адреса в которых выровнены к критичному фундаментальному типу процессора (например, для ARM7 Atmel фундаментальный тип это 32 битное слово, тип int или unsigned int).
Спецификации для языка C и C++ устанавливают, что само существование и природа таких выравнивающих дополнительных байт полностью "implementation defined", т. е. определено реализацией компилятора и платформы, на которой выполняется код. Это означает, что каждая комбинация процессор + операционная система + компилятор свободны в использовании любого выравнивания, которое лучше всего подходит для наиболее эффективного целевого кода. Предполагается, что программисты (в обычных случаях) не учитывают определенные правила для дополнения данных пустыми байтами и выравнивания адресов. Таким образом, в сами языки не предусматривают индикацию специальной поддержки выравнивания и добавления пустых байт, хотя многие компиляторы (включая gcc и IAR) имеют нестандартные расширения, чтобы позволить управление выравниванием (в IAR версии 4 это директива #pragma pack, в более поздних версиях IAR это атрибут __packed, подробнее см. [2]).
Таким образом "выравнивание структуры" это выбор правил, задающих, где и когда добавляются пустые байты (padding), чтобы мог быть сгенерирован более эффективный код для процессора.
[Почему неправильное выравнивание может вызвать проблемы?]
Ранние процессоры ARM имели очень ограниченные возможности доступа к ячейкам памяти, которые расположены по не выровненному на 4 байта адресу (т. е. когда адрес переменной не делится нацело на 4). Более современные процессоры ARM (в противоположность StrongArm) имеют урезанную поддержку доступа к половинкам слова (т. е. к 16-разрядным значениям short int). Первые компиляторы разрабатывались для использования кода в приложениях встраиваемых систем. Разработчики компилятора выбирали разрешить декларировать типы короче, чем слово 32 бита (типы char и short), но выравнивали при этом все структуры по границе 32-битного слова, чтобы увеличить быстродействие при доступе ко всем переменным программы.
Эти правила отлично соответствуют спецификациям языков C and C++, но они отличаются от правил, виртуально используемых для всех других 32-разрядных и 64-разрядных микропроцессоров. Операционная система Linux и её приложения ранее редко портировались на платформы с особыми правилами выравнивания, так что в коде могут быть скрытые дефекты, связанные с неучтенными правилами выравнивания определенных типов данных. Эти дефекты проявились, когда приложения начали портировать на платформу ARM.
Эти скрытые дефекты могут привести с снижению быстродействия, повреждению данных и к сбою программы. Конечный эффект зависит от того, как сконфигурированы компилятор и операционная система, а также от природы дефектного кода. Есть 3 способа исправить такие скрытые дефекты:
1. Изменить для компилятора правила выравнивания, чтобы они соответствовали используемому приложению (или другим операционным системам Linux). 2. Использовать перехват ошибки выравнивания (обработку прерывания исключения), чтобы исправить некорректные обращения к памяти. 3. Найти и исправить все скрытые дефекты, каждый по отдельности.
Эти 3 альтернативы в некоторой степени исключают друг друга. У каждой есть определенные достоинства и недостатки. Все могут быть успешно применены, но "правильное" решение, конечно, зависит от Ваших предпочтений и конкретной задачи.
[Ссылки]
1. Библиотека FatFS: модуль файловой системы FAT. 2. IAR EW ARM: выравнивание полей в структурах. |