VisualDSP: полезные директивы #pragma |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||||||||||||||||
В этой статье приведены наиболее часто используемые директивы #pragma. Подробнее про директивы #pragma написано в руководстве [1], раздел Compiler -> C/C++ Compiler Language Extensions -> Pragmas. [Общие сведения] Компилятор Blackfin C/C++ поддерживает прагмы. Это специальные директивы, которые модифицируют поведение компилятора. Есть 2 способа использования прагм: директивы pragma и операторы pragma. Директивы pragma имеют следующий синтаксис: #pragma pragma-directive pragma-directive-operands new-line Операторы pragma имеют следующий синтаксис: _Pragma (string-literal) Когда обрабатывается оператор pragma, компилятор превращает его в директиву pragma directive, используя не строковую версию операнда строкового литерала string-literal. Это означает, что следующая директива pragma: #pragma linkage_name mylinkname может быть также быть заменена следующим эквивалентным оператором pragma operator: _Pragma ("linkage_name mylinkagename") Примеры в этом руководстве используют форму директивы. Компилятор языка C поддерживает прагмы для следующих целей: • Управление выравниванием данных. Компилятор выдает предупреждения, когда он встречает не распознанные директивы или операторы pragma. [Управление выравниванием данных] Может использоваться перед различными декларациями структур и полей. Прикладывается непосредственно к переменной, которая сразу следует за директивой #pragma align. Синтаксис: #pragma align num Эта прагма дает эффект, что следующая за ней переменная или поле принудительно будут выровнены по границе адреса блока памяти, как это указано числовым параметром num, следующим образом: • Если pragma прикладывается к локальной переменной (которая сохраняется в стеке), то выравнивание переменной будет изменено только в том случае, когда число num не больше, чем выравнивание стека, т. е. 4 байта. Если же num больше выравнивания стека, то компилятор выдаст предупреждение, и pragma будет игнорирована. • Если num больше, чем выравнивание, которое нормально требуется для выравнивания следующей декларируемой переменной или поля, то переменная или поле будут выравнены в памяти по границе с адресом, кратным num. • Если num меньше, чем нормально требуемое выравнивание, то выравнивание переменной или поля при декларации будет изменено в соответствии с num, и компилятором будет выдано предупреждение, что выравнивание было уменьшено. Вместо числового параметра num, который означает кратность адреса в байтах, могут быть указаны ключевые слова, задающие возможные выравнивания: _WORD задает 32-разрядное выравнивание (соответствует num==4) Если в настоящий момент активно действие директив #pragma pack или #pragma pad pragma (см. врезки далее), то выравнивание этих директив будет изменено непосредственно следующей декларацией поля. В следующих примерах показано, как использовать #pragma align. struct s{
#pragma align 8 /* поле a выравнено по 8-байтной границе адреса */ int a; int bar;
#pragma align 16 /* поле b выровнено по 16-байтной границе адреса */ int b; } t[2]; #pragma align 256
int arr[128]; /* декларируется массив чисел int с выравниванием 256 */ Следующий пример показывает допустимую директиву, однако на неё будет выдано предупреждение компилятора. #pragma align 1
int warns; /* декларируется int с байтовым выравниванием, */ /* что вызывает предупреждение компилятора */ Следующий пример показывает недопустимое использование #pragma align. Поскольку выравнивание не является степенью числа 2, компилятор не позволит его использовать и выдаст ошибку. #pragma align 3
int errs; /* НЕДОПУСТИМО: декларируется int с выравниванием, не являющимся */ /* степенью двойки, компилятор выдаст ошибку */ Внимание: #pragma align прикладывается только с непосредственно следующей переменной при декларации, даже если она является частью списка. Например: #pragma align 8
int i1, i2, i3; // pragma align будет применена только к переменной i1 Директива pack используется для упаковки полей структуры. Синтаксис: #pragma pack (alignopt) Прагма pack может применяться к определениям структур (struct). Она прикладывается ко всем определениям структур, которые следуют после неё, пока не будет восстановлено выравнивание по умолчанию, когда в директиве пропущен параметр alignopt (например, когда задана #pragma pack() с пустыми круглыми скобками). Прагма pack используется для уменьшения выравнивания по умолчанию в структуре до значения alignopt. Если поля в структуре имеют выравнивание по умолчанию больше, то их выравнивание будет уменьшено до alignopt. Если поля в структуре имеют выравнивание меньше alignopt, то их выравнивание останется без изменения. Если указано значение alignopt, то нельзя использовать #pragma pad, пока не будет восстановлено значение выравнивания по умолчанию. Компилятор генерирует сообщение об ошибке, если прагмы pad и pack используются так, что получается конфликт. В следующем примере показано, как использовать #pragma pack: #pragma pack(1)
/* Минимальное выравнивание в структуре теперь 1 байт, так что использование
"#pragma pad" теперь приведет к ошибке компиляции */
struct is_packed {
char a;
/* в этом месте обычно компилятор добавил бы 3 лишних байта для выравнивания,
но теперь этого не будет, потому что ранее была использована pragma pack(1) */ int b; } t[2]; /* упакованное определение t займет 10 байт */ #pragma pack()
/* Теперь минимальное выравнивание структуры не 1 байт, struct is_packed u[2]; /* определение u требует 10 упакованных байт */ /* Далее идет структура not_packed, которая задает новый тип,
так что она не будет упакована. */
struct not_packed {
char a;
/* в этом месте компилятор вставит 3 лишних байта */ int b; } w[2]; /* определение w потребует 16 байт */ Процессор Blackfin не поддерживает доступы к не выровненным участкам памяти на аппаратном уровне; поэтому компилятор генерирует дополнительные код, чтобы корректно обработать чтения из не выровненных полей структуры (и записи в них). Сгенерированный код не обязательно будет таким же эффективным, как и чтение/запись выровненных полей, но это вынужденный компромисс, на который приходится идти для доступа к упакованным структурам. Компилятором будет корректно обработаны только прямые чтения/записи не выровненных полей структуры. В результате, если взять адрес от не выровненного поля и присвоить его указателю, то это приведет к выдаче предупреждения компилятором. Причина предупреждения в том, что обращение по такому указателю не будет детектировано компилятором и может привести к ошибке, если адрес не выровненного поля получен и сохранен в указателе другого типа для той же структуры. Поскольку #pragma pack уменьшает требования к выравниванию, и таким образом уменьшает необходимость для добавления нулевых байт к полям структуры, общий размер структуры может быть уменьшен; фактически именно это уменьшение как раз и является причиной использования этой прагмы. Однако имейте в виду, что уменьшенное выравнивание может быть применено к структуре в целом, так что экземпляры структуры могут начинаться на границах alignopt вместо границ по умолчанию, эквивалентных для не упакованных структур. Для предыдущей версии VisualDSP++ 4.0 это работало не так. Компилятор уменьшал внутреннее выравнивание, но сохранял общее выравнивание. Начиная с VisualDSP++ 4.0 упакованные структуры могут начаться с разных границ от не упакованных структур. Чтобы сохранить общее начальное выравнивание, используйте #pragma align на первом поле структуры. Синтаксис: #pragma pad (alignopt) Прагма pad pragma может применяться к определениям структур. Она прикладывается к определениям структур, которые следуют выравниванию по умолчанию, восстановленному пропуском alignopt (например, #pragma pad() с пустыми скобками). Прагма pad является эффективной заменой для размещения #pragma align перед каждым полем внутри определения структуры. Наподобие прагмы pack, она уменьшает выравнивание полей, у которых по умолчанию выравнивание больше, чем alignopt. Однако, в отличие от прагмы pack, она также увеличивает выравнивание полей, которые по умолчанию меньше, чем alignopt. Также #pragma pack (alignopt) выдает предупреждение, когда выравнивание поля уменьшено, а #pragma pad (alignopt) этого не делает. Если указан параметр alignopt, то нельзя использовать #pragma pack, пока не будет восстановлено выравнивание по умолчанию. Следующий пример показывает, как использовать #pragma pad(). #pragma pad(4)
struct { int i; int j; } s = {1,2}; #pragma pad()
[Управление диагностикой] Компилятор поддерживает директивы #pragma diag, которые позволяют селективный выбор важности диагностических сообщений компилятора. У этой директивы есть 3 формы: • Модификация важности отдельных диагностик. Модификация важности отдельных диагностик. Эта форма директивы имеет следующий синтаксис: #pragma diag(ACTION: DIAG [, DIAG ...][: STRING]) Здесь ACTION является квалификатором, который может быть одним из ключевых слов, перечисленных в таблице 1-32. Таблица 1-32. Ключевые слова для квалификатора ACTION.
Если не используется режим MISRA-C, то квалификатор DIAG может быть одним или большим количеством перечисленных через запятую номеров диагностических сообщений компилятора без предшествующих префиксов "cc" или нулей. Выбор номеров ошибок ограничен теми, у которых может быть изменена их важность (это те, которые отображают "{D}" в сообщении об ошибке). Дополнительно некоторые диагностики являются глобальными (например, диагностики, выданные бакэндом компилятора после лексического анализа и парсинга, или перед началом парсинга), и у этих глобальных диагностик нельзя поменять их важность через прагмы, управляющие диагностикой. Чтобы модифицировать важность глобальных диагностик, используйте опции командной строки, управляющие диагностикой. Подробнее см. описание опции -W{error|remark|suppress|warn}. В режиме MISRA-C квалификатор DIAG является списком номеров правил MISRA-C в форме misra_rule_6_3 и misra_rule_19_4 для правил 6.3 и 19.4, и так далее. Правила 10.1 и 10.2 являются специальным случаем, в котором оба правила разделяются на 4 отдельные проверки правил. Например, 10.1(c) должно быть обозначено как misra_rule_10_1_c. Третий, не обязательный аргумент это строковый литерал для вставки комментария, относящегося к использованию #pragma diag. Модификация поведения целого класса диагностик. Эта форма директивы имеет следующий синтаксис, который не разрешен в режиме MISRA-C: #pragma diag(ACTION) Эффект получается следующий: #pragma diag(errors). Эта прагма может быть использована, чтобы запретить все последующие сообщения и ремарки (эквивалентна опции -w командной строки). #pragma diag(remarks). Эта прагма может быть использована для разрешения последующих ремарок и предупреждений (эквивалентна опции -Wremarks командной строки). #pragma diag(warnings). Эта прагма может быть использована для восстановления поведения по умолчанию, когда не заданы ни одна из опций -w или -Wremarks, которые отображают предупреждения, но запрещают ремарки. Сохранение или восстановление текущего поведения всех диагностик. Эта прагма имеет следующий синтаксис: #pragma diag(push) Эффект получается следующий: #pragma diag(push). Эта прагма может использоваться для сохранения текущего состояния важности всех диагностических сообщений об ошибках. #pragma diag(pop). Эта прагма восстанавливает все диагностические сообщения, которые были ранее сохранены ближайшим предыдущим push. Все директивы #pragma diag(push) должны соответствовать такому же количеству последующих директив #pragma diag(pop) всего юнита трансляции, но не должны быть соответствующими в отдельных исходных файлах, за исключением режима MISRA-C. Обратите внимание, что порог ошибки (установленный на ключевые слова remarks, warnings или errors) также сохранится и восстановится этими директивами. Длительность таких модификаций важности диагностики продолжается от следующей строки за директивой pragma или до конца юнита трансляции, или до следующей директивы #pragma diag(pop), или до следующей директивы #pragma diag() с тем же номером ошибки. Эти прагмы могут использоваться могут использоваться в любых местах кода, и на них не влияют обычные правила области видимости (normal scoping rules). Сначала обрабатываются все изменения диагностики, задаваемые из командной строки, и любые последующие директивы #pragma diag() получают приоритет, когда действие восстановления откатывает обработку важности обратно к моменту начала компиляции, когда были обработаны опции командной строки. Примечание: директивы для модификации отдельных диагностик сингулярны (упоминаются в единственном числе, например "error"), и директивы для модификации классов диагностик плюральны (во множественном числе, "errors"). [Передача компилятору атрибутов] Прагма file_attr указывает компилятору выдать специальные атрибуты, когда он обрабатывает файл, содержащий эту прагму. В одной строке допускается использовать несколько директив #pragma file_attr. Синтаксис: #pragma file_attr("name[=value]" [, "name[=value]" [...]]) Если опущено "=value", то будет использоваться значение по умолчанию "1". Значение атрибута это все символы, которые следуют после символа '=' и перед закрывающего символа ", включая пробелы. Будет выдано предупреждение, если были предшествующие или завершающие пробелы в значении атрибута, поскольку это скорее всего будет ошибкой. Подробнее про атрибуты см. раздел "File Attributes" руководства [1]. Также см. [3]. Прагмы управления файлами заголовка (hdrstop, no_implicit_inclusion, no_pch, once и system_header) помогают компилятору обрабатывать заголовочные файлы (header files). Прагма hdrstop используется вместе с опцией -pch (precompiled header). Опция -pch инструктирует компилятор на просмотр предварительно скомпилированного заголовка (precompiled header, файл с расширением .pch file), и, если он не найден, генерировать файл для использования в последующих компиляциях. Файл .pch содержит снимок всего кода, предшествующего точке header stop. По умолчанию точка header stop это первая точка в модуле кода, где заканчивается препроцессинг. Директива #pragma hdrstop может использоваться для установки точки пораньше. В следующем примере точка header stop по умолчанию header stop находится в начале декларации переменной i. #include "standard_defs.h"
#include "common_data.h"
#include "frequently_changing_data.h"
int i;
Такой вариант может быть не очень хорошим выбором, потому что если в файле frequently_changing_data.h могут быть часто изменяемые данные, то это приведет к частой регенерации файла .pch, и в результате получится потеря выгоды от использования precompiled header. Тогда hdrstop pragma может использоваться для переноса точки header stop в более подходящее место. В следующем примере так и сделано - файл предварительно скомпилированных заголовков не будет включать содержимое frequently_changing_data.h, потому что он подключается после прагмы hdrstop, так что не нужно будет регенерировать precompiled header всякий раз, когда был модифицирован frequently_changing_data.h. #include "standard_defs.h"
#include "common_data.h"
#pragma hdrstop
#include "frequently_changing_data.h"
int i;
[Управление оптимизацией] В коде можно применять директивы all_aligned, different_banks, extra_loop_loads, optimize_off, optimize_for_speed и другие директивы. Подробнее см. [4]. [Управление линкером] Прагма retain_name показывает, что декларация функции или переменной, которая сразу следует за этой прагмой, не должна быть удалена линкером, если нигде не используется в коде. Обычно если разрешен межпроцедурный анализ (IPA), или если линкеру разрешено удалять неиспользуемые функции или переменные (linker elimination), то инструментарий VisualDSP++ определит неиспользуемые функции и переменные, и удалит их из результирующего выходного исполняемого кода с целью экономии памяти. Прагме retain_name инструктирует инструментарий, что указанный код/переменная должны быть сохранены. Следующий пример показывает, как использовать эту прагму. int delete_me(int x) { return x-2; } #pragma retain_name
int keep_me(int y) { return y+2; } int main(void) { return 0; } Поскольку программа не использует ни функцию delete_me(), ни keep_me(), то обычно компилятор удалил бы из выходного кода обе эти функции. Но в данном примере компилятор удалит только функцию delete_me(), но функция keep_me() будет сохранена, потому что перед ней стоит эта прагма retain_name. Для функции main() директива retain_name не нужна. Эта прагма допустима только для глобальных символов. Она недопустима для следующего типа символов: • Символы со статически классом размещения (static storage class). Подробнее про межпроцедурный анализ (IPA) см. [5]. [Управление размещением кода и данных] Прагмы компилятора section и default_section предоставляют качественный контроль над секциями памяти, куда компилятор помещает символы. Прагма section(СТРОКАСЕКЦИИ [, QUALIFIER, ...]) используется для переопределения целевой секции для любого глобального или статического символа, который сразу за ней следует. Прагма позволяет лучше управлять квалификатором секции в сравнении с ключевым словом section. Прагма default_section(ВИДСЕКЦИИ [, СТРОКАСЕКЦИИ [, QUALIFIER, ...]]) используется для переназначения секций по умолчанию, в которые компилятор помещает свои символы. Секции по умолчанию попадают в категории, перечисленные под ВИДСЕКЦИИ. За исключением категории STI, эта прагма остается в силе для категории секции до её следующего использования с отдельной категорией, или до конца файла. STI является исключением, только в одном STI можно определить default_section, и его область действия весь файл, не только часть после использования STI. Будет выдаваться предупреждение, если в одном и том же файле будет указано несколько секций STI. Пропуск имени секции приводит к тому, секция по умолчанию будет сброшена к секции, которая была в использовании с начала файла, которая может быть либо значением по умолчанию для компилятора, либо значением, установленным пользователем через опцию командной строки -section (например, -section ВИДСЕКЦИИ=СТРОКАСЕКЦИИ). Во всех случаях (включая STI), прагма default_section перезаписывает значение, указанное опцией -section в командной строке. #pragma default_section(DATA, "NEW_DATA1")
int x;
#pragma default_section(DATA, "NEW_DATA2")
int x=5; #pragma default_section(DATA, "NEW_DATA3")
int x;
В этом случае x размещается в NEW_DATA2 потому, что определение x попадает в её действие. Прагма default_section может быть использована только в глобальной области действия, где разрешены глобальные переменные. ВИДСЕКЦИИ может быть одним из ключевых слов, перечисленных в таблице 1-29. Таблица 1-29. Виды секций для параметра ВИДСЕКЦИИ.
СТРОКАСЕКЦИИ является строкой, заключенной в двойные кавычки, содержащей имя секции, полностью как она появляется в файле ассемблера. Изменение одного вида секции не влияет на другие виды секции. Например, даже при том, что виды секций STRINGS и CONSTDATA по умолчанию помещаются компилятором в одну и ту же секцию, если изменена секция по умолчанию для CONSTDATA, то это никак не повлияет на данные STRINGS. Обратите внимание, что ALLDATA не реальная секция, а псевдо-вид, который соответствует DATA, CONSTDATA, STRINGS, AUTOINIT и BSZ. Изменение ALLDATA эквивалентно изменению всех этих видов секций. Таким образом, #pragma default_section(ALLDATA, params)
будет эквивалентно последовательности: #pragma default_section(DATA, params)
#pragma default_section(CONSTDATA, params)
#pragma default_section(STRINGS, params)
#pragma default_section(AUTOINIT, params)
#pragma default_section(BSZ, params)
QUALIFIER может быть одним из ключевых слов, перечисленных в таблице 1-30. Таблица 1-30. Ключевые слова для QUALIFIER.
Может быть любое количество квалификаторов секции, следующих друг за другом через запятую с такими прагмами, но они не должны конфликтовать друг с другом. Квалификаторы также должны быть непротиворечивыми по прагмам для одинаковых имен секций, и пропуск квалификаторов не допускается, даже если как минимум один такой квалификатор появлялся в предыдущей прагме для той же секции. Если не указан какой-либо квалификатор для отдельной секции к концу блока трансляции, компилятор использует квалификаторы по умолчанию, подходящие для целевого процессора. В следующем коде указано, что f() должна быть размещена в секции foo, квалифицированной как DOUBLEANY: #pragma section("foo", DOUBLEANY)
void f() {} Компилятор всегда пытается соблюдать прагму section как с высшим приоритетом, и прагма default_section всегда имеет приоритет меньше. Например, следующий код в результате приведет к тому, что функция f будет размещена в секции foo: #pragma default_section(CODE, "bar")
#pragma section("foo")
void f() {} В следующем коде x будет помещен в секции zeromem: #pragma default_section(BSZ, "zeromem")
int x;
Примечание: в случаях, когда объект C++ STL требуется разместить в какой-то определенной секции памяти, использование #pragma section/default_section не работает. Вместо этого должна использоваться куча не по умолчанию (non-default heap), как это разъяснено в руководстве "Allocating C++ STL Objects to a Non-Default Heap". [Ссылки] 1. VisualDSP++ 5.0 C/C++ Compiler and Library Manual for Blackfin® Processors site:analog.com. |