Preprocessor directive expected что это
Preprocessor directives
Preprocessor directives are lines included in the code of programs preceded by a hash sign ( # ). These lines are not program statements but directives for the preprocessor. The preprocessor examines the code before actual compilation of code begins and resolves all these directives before any code is actually generated by regular statements.
These preprocessor directives extend only across a single line of code. As soon as a newline character is found, the preprocessor directive is ends. No semicolon ( ; ) is expected at the end of a preprocessor directive. The only way a preprocessor directive can extend through more than one line is by preceding the newline character at the end of the line by a backslash ( \ ).
macro definitions (#define, #undef)
#define can work also with parameters to define function macros:
This would replace any occurrence of getmax followed by two arguments by the replacement expression, but also replacing each argument by its identifier, exactly as you would expect if it was a function:
Defined macros are not affected by block structure. A macro lasts until it is undefined with the #undef preprocessor directive:
This would generate the same code as:
This would be translated into:
The operator ## concatenates two arguments leaving no blank spaces between them:
This would also be translated into:
Because preprocessor replacements happen before any C++ syntax check, macro definitions can be a tricky feature. But, be careful: code that relies heavily on complicated macros become less readable, since the syntax expected is on many occasions different from the normal expressions programmers expect in C++.
Conditional inclusions (#ifdef, #ifndef, #if, #endif, #else and #elif)
These directives allow to include or discard part of the code of a program if a certain condition is met.
#ifdef allows a section of a program to be compiled only if the macro that is specified as the parameter has been defined, no matter which its value is. For example:
#ifndef serves for the exact opposite: the code between #ifndef and #endif directives is only compiled if the specified identifier has not been previously defined. For example:
In this case, if when arriving at this piece of code, the TABLE_SIZE macro has not been defined yet, it would be defined to a value of 100. If it already existed it would keep its previous value since the #define directive would not be executed.
Line control (#line)
When we compile a program and some error happens during the compiling process, the compiler shows an error message with references to the name of the file where the error happened and a line number, so it is easier to find the code generating the error.
The #line directive allows us to control both things, the line numbers within the code files as well as the file name that we want that appears when an error takes place. Its format is:
#line number «filename»
Where number is the new line number that will be assigned to the next code line. The line numbers of successive lines will be increased one by one from this point on.
«filename» is an optional parameter that allows to redefine the file name that will be shown. For example:
Error directive (#error)
This directive aborts the compilation process when it is found, generating a compilation error that can be specified as its parameter:
This example aborts the compilation process if the macro name __cplusplus is not defined (this macro name is defined by default in all C++ compilers).
Source file inclusion (#include)
This directive has been used assiduously in other sections of this tutorial. When the preprocessor finds an #include directive it replaces it by the entire content of the specified header or file. There are two ways to use #include :
The syntax used in the second #include uses quotes, and includes a file. The file is searched for in an implementation-defined manner, which generally includes the current path. In the case that the file is not found, the compiler interprets the directive as a header inclusion, just as if the quotes ( «» ) were replaced by angle-brackets ( <> ).
Pragma directive (#pragma)
Predefined macro names
The following macro names are always defined (they all begin and end with two underscore characters, _ ):
Директивы препроцессора
В этом уроке мы узнаем о директивах препроцессора C#, а также о том, когда, почему и как они используются.
Как следует из названия, директивы препроцессора представляют собой блок операторов, который обрабатывается до начала компиляции.
Директивы препроцессора — это команды компилятора, которые влияют на процесс его работы.
Эти команды определяют, какие блоки кода нужно компилировать или как, например, обрабатывать определенные ошибки и предупреждения.
Директива
Описание
Синтаксис
Проверяет, является ли препроцессорное выражение истинным или нет.
Используется вместе с #if для проверки нескольких препроцессорных выражений.
Используется вместе с #if для создания условных ветвлений.
Используется вместе с #if для обозначения конца условной директивы.
Используется для определения идентификатора
Используется для отмены определения идентификатора.
Позволяет генерировать предупреждение 1 уровня из кода.
Позволяет генерировать ошибку из кода.
Используется для задания номера строки и имени файла, сообщаемого макросами препроцессора.
Позволяет обозначить область, которую можно развернуть или свернуть при использовании редактора кода Visual Studio.
Дает компилятору специальные инструкции для компиляции файла, в котором он работает.
Синтаксис
Пример использования
Здесь TESTING — это идентификатор.
Директива #if
Синтаксис
Пример использования
Пример 1. Используем директиву #if
Вывод:
В приведенной выше программе идентификатор CSHARP определяется с помощью директивы #define в начале программы. Внутри метода Main() директива #if используется для проверки истинности CSHARP. Блок кода внутри директивы #if компилируется, только если CSHARP определен.
Директива #elif
Синтаксис
Пример использования
Директива #else
Синтаксис
Пример использования
Директива #endif
Синтаксис
Пример использования
Пример 2. Используем условные директивы (#if, #elif, #else, #endif)
Вывод:
Директива #warning
Синтаксис
Пример 3. Используем директиву #warning
Вывод:
Обратите внимание, что операторы после директивы #warning также выполняются. Это означает, что директива #warning не завершает программу, а просто выдает предупреждение.
Директива #error
Синтаксис
Пример 4. Используем директиву #error
Вывод:
Директива #line
Синтаксис
Пример использования
Пример 5. Используем директиву #line
Вывод:
Директива #region и #endregion
Синтаксис
Пример 6. Используем директиву #region
Вывод:
Директива #pragma
Синтаксис
Пример использования
Пример 7. Используем директиву #pragma
Вывод:
Мы видим, что на экране отображается только второе предупреждение.
Так происходит, потому что мы изначально отключили все предупреждения перед первым предупреждением и восстановили их только перед вторым. По этой причине первое предупреждение было скрыто.
Мы также можем отключить конкретное предупреждение вместо всех предупреждений.
Директивы препроцессора C#
Хотя у компилятора нет отдельного препроцессора, директивы, описанные в этом разделе, обрабатываются так, как если бы он был. Они используются в условной компиляции. В отличие от директив C и C++ вы не можете использовать их для создания макросов. Директива препроцессора должна быть единственной инструкцией в строке.
Контекст, допускающий значение NULL
Директива препроцессора #nullable устанавливает контекст с заметками о допустимости значений NULL и контекст с предупреждениями о допустимости значений NULL. Эта директива определяет, действуют ли заметки, допускающие значение NULL, и могут ли быть заданы предупреждения о допустимости значений NULL. Каждый контекст либо отключен, либо включен.
Оба контекста можно указать на уровне проекта (за пределами исходного кода C#). Директива #nullable управляет контекстами заметок и предупреждений и имеет приоритет над параметрами уровня проекта. Директива задает контексты, которыми управляет, пока другая директива не переопределит ее, или до конца исходного файла.
Ниже приведены результаты использования директив:
Условная компиляция
Для управления условной компиляцией используются четыре директивы препроцессора.
Для традиционных проектов, в которых не используется пакет SDK, необходимо вручную настроить символы условной компиляции для различных целевых платформ в Visual Studio с помощью страниц свойств проекта.
В следующем примере показано, как тестировать разные целевые платформы для использования более новых интерфейсов API, когда это возможно:
Определение символов
Используйте следующие две директивы препроцессора, чтобы определить или отменить определение символов для условной компиляции.
Директиву #define нельзя использовать для объявления значений констант, как это обычно делается в C и C++. Для определения констант в C# следует использовать статические элементы класса или структуры. При наличии нескольких констант имеет смысл создать для них отдельный класс «Constants».
Определение областей
Вы можете определить области кода, которые можно свернуть в структуру, используя следующие две директивы препроцессора.
Директива #region позволяет указать блок кода, который можно разворачивать и сворачивать с помощью функции структурирования в редакторе кода. В больших файлах кода удобно сворачивать или скрывать одну область или несколько, чтобы не отвлекаться от той части файла, над которой в настоящее время идет работа. В следующем примере показано, как определить область:
Сведения об ошибках и предупреждениях
Вы указываете компилятору создавать определенные пользователем ошибки и предупреждения компилятора, а также управлять сведениями о строках с помощью следующих директив.
#error позволяет создать определяемую пользователем ошибку CS1029 из определенного места в коде. Пример:
Компилятор обрабатывает #error version особым образом и сообщает об ошибке компилятора CS8304 с сообщением, содержащим используемые версии компилятора и языка.
#warning позволяет создать предупреждение компилятора CS1030 первого уровня из определенного места в коде. Пример:
Директива #line позволяет изменять номер строки компилятора и при необходимости имя файла, в который будут выводиться ошибки и предупреждения.
В следующем примере показано, как включить в отчет два предупреждения, связанные с номерами строк. Директива #line 200 принудительно устанавливает номер следующей строки 200 (по умолчанию используется номер 6). До выполнения следующей директивы #line в отчете будет указываться имя файла Special. Директива #line default по умолчанию восстанавливает нумерацию строк в исходное состояние с учетом строк, номера которых были изменены с помощью предшествующей директивы.
В результате компиляции формируются следующие результаты:
Директива #line hidden скрывает последующие строки для отладчика. В этом случае при пошаговой проверке кода разработчиком все строки между #line hidden и следующей директивой #line (кроме случаев, когда это также директива #line hidden ) будут пропущены. Этот параметр также можно использовать для того, чтобы дать ASP.NET возможность различать определяемый пользователем и создаваемый компьютером код. В основном эта функция используется в ASP.NET, но также может быть полезна и в других генераторах исходного кода.
Директива #line hidden не влияет на имена файлов и номера строк в отчетах об ошибках. Это значит, что при обнаружении ошибки в скрытом блоке компилятор укажет в отчете текущие имя файла и номер строки, где найдена ошибка.
Директива #line filename задает имя файла, которое будет отображаться в выходных данных компилятора. По умолчанию используется фактическое имя файла с исходным кодом. Имя файла должно заключаться в двойные кавычки (» «). Перед ним должен указываться номер строки.
Начиная с C# 10 можно использовать новую форму директивы #line :
Компоненты этой формы:
В предыдущем примере будет создано следующее предупреждение:
После повторного сопоставления переменная b находится в первой строке, в шестом символе.
Предметно-ориентированные языки (DSL) обычно используют этот формат, чтобы обеспечить более эффективное сопоставление исходного файла с созданными выходными данными C#. Дополнительные примеры этого формата см. в разделе примеров в спецификации функции.
Директивы pragma
Директива #pragma предоставляет компилятору специальные инструкции для компиляции файла, в котором она появляется. Компилятор должен поддерживать эти инструкции. Другими словами, директиву #pragma невозможно использовать для создания настраиваемых инструкций предварительной обработки.
pragma-name — имя распознанной прагмы, а pragma-arguments — аргументы, относящиеся к прагме.
#pragma warning
#pragma warning может включать или отключать определенные предупреждения.
warning-list — список номеров предупреждений с разделителем-запятой. Префикс CS является необязательным. Если номера предупреждений не указаны, disable отключает все предупреждения, а restore включает все предупреждения.
Чтобы найти номера предупреждений в Visual Studio, выполните сборку проекта, а затем поиск номеров предупреждений в окне Вывод.
#pragma checksum
Создает контрольные суммы для исходных файлов, чтобы помочь с отладкой страниц ASP.NET.
«filename» — это имя файла, для которого требуется наблюдение за изменениями или обновлениями, «
Отладчик Visual Studio использует контрольную сумму, чтобы подтвердить нахождение правильного источника. Компилятор вычисляет контрольную сумму для исходного файла, а затем передает результат в файл базы данных (PDB) программы. Отладчик затем использует PDB-файл для сравнения с контрольной суммой, вычисленной им для исходного файла.
Это решение не работает для проектов ASP.NET, так как рассчитанная контрольная сумма относится к созданному исходному файлу, а не файлу ASPX. Чтобы решить эту проблему, #pragma checksum предоставляет поддержку контрольных сумм для страниц ASP.NET.
При создании проекта ASP.NET в Visual C# созданный исходный файл содержит контрольную сумму для ASPX-файла, из которого создается источник. Затем компилятор записывает эти данные в PDB-файл.
Если компилятор не обнаруживает директиву #pragma checksum в файле, он вычисляет контрольную сумму и записывает значение в PDB-файл.
C# preprocessor directives
Although the compiler doesn’t have a separate preprocessor, the directives described in this section are processed as if there were one. You use them to help in conditional compilation. Unlike C and C++ directives, you can’t use these directives to create macros. A preprocessor directive must be the only instruction on a line.
Nullable context
The #nullable preprocessor directive sets the nullable annotation context and nullable warning context. This directive controls whether nullable annotations have effect, and whether nullability warnings are given. Each context is either disabled or enabled.
Both contexts can be specified at the project level (outside of C# source code). The #nullable directive controls the annotation and warning contexts and takes precedence over the project-level settings. A directive sets the context(s) it controls until another directive overrides it, or until the end of the source file.
The effect of the directives is as follows:
Conditional compilation
You use four preprocessor directives to control conditional compilation:
When the C# compiler finds an #if directive, followed eventually by an #endif directive, it compiles the code between the directives only if the specified symbol is defined. Unlike C and C++, you can’t assign a numeric value to a symbol. The #if statement in C# is Boolean and only tests whether the symbol has been defined or not. For example:
#endif specifies the end of a conditional directive, which began with the #if directive.
For traditional, non-SDK-style projects, you have to manually configure the conditional compilation symbols for the different target frameworks in Visual Studio via the project’s properties pages.
The following example shows you how to define a MYTEST symbol on a file and then test the values of the MYTEST and DEBUG symbols. The output of this example depends on whether you built the project on Debug or Release configuration mode.
The following example shows you how to test for different target frameworks so you can use newer APIs when possible:
Defining symbols
You use the following two preprocessor directives to define or undefine symbols for conditional compilation:
The #define directive cannot be used to declare constant values as is typically done in C and C++. Constants in C# are best defined as static members of a class or struct. If you have several such constants, consider creating a separate «Constants» class to hold them.
Defining regions
You can define regions of code that can be collapsed in an outline using the following two preprocessor directives:
#region lets you specify a block of code that you can expand or collapse when using the outlining feature of the code editor. In longer code files, it’s convenient to collapse or hide one or more regions so that you can focus on the part of the file that you’re currently working on. The following example shows how to define a region:
A #region block must be terminated with an #endregion directive. A #region block can’t overlap with an #if block. However, a #region block can be nested in an #if block, and an #if block can be nested in a #region block.
Error and warning information
You instruct the compiler to generate user-defined compiler errors and warnings, and control line information using the following directives:
#error lets you generate a CS1029 user-defined error from a specific location in your code. For example:
The compiler treats #error version in a special way and reports a compiler error, CS8304, with a message containing the used compiler and language versions.
#warning lets you generate a CS1030 level one compiler warning from a specific location in your code. For example:
#line lets you modify the compiler’s line numbering and (optionally) the file name output for errors and warnings.
The following example shows how to report two warnings associated with line numbers. The #line 200 directive forces the next line’s number to be 200 (although the default is #6), and until the next #line directive, the filename will be reported as «Special». The #line default directive returns the line numbering to its default numbering, which counts the lines that were renumbered by the previous directive.
Compilation produces the following output:
The #line hidden directive hides the successive lines from the debugger, such that when the developer steps through the code, any lines between a #line hidden and the next #line directive (assuming that it isn’t another #line hidden directive) will be stepped over. This option can also be used to allow ASP.NET to differentiate between user-defined and machine-generated code. Although ASP.NET is the primary consumer of this feature, it’s likely that more source generators will make use of it.
A #line hidden directive doesn’t affect file names or line numbers in error reporting. That is, if the compiler finds an error in a hidden block, the compiler will report the current file name and line number of the error.
The #line filename directive specifies the file name you want to appear in the compiler output. By default, the actual name of the source code file is used. The file name must be in double quotation marks («») and must be preceded by a line number.
Beginning with C# 10, you can use a new form of the #line directive:
The components of this form are:
The preceding example would generate the following warning:
Domain-specific languages (DSLs) typically use this format to provide a better mapping from the source file to the generated C# output. To see more examples of this format, see the feature specification in the section on examples.
Pragmas
#pragma gives the compiler special instructions for the compilation of the file in which it appears. The instructions must be supported by the compiler. In other words, you can’t use #pragma to create custom preprocessing instructions.
Where pragma-name is the name of a recognized pragma and pragma-arguments is the pragma-specific arguments.
#pragma warning
#pragma warning can enable or disable certain warnings.
Where warning-list is a comma-separated list of warning numbers. The «CS» prefix is optional. When no warning numbers are specified, disable disables all warnings and restore enables all warnings.
To find warning numbers in Visual Studio, build your project and then look for the warning numbers in the Output window.
#pragma checksum
Generates checksums for source files to aid with debugging ASP.NET pages.
Where «filename» is the name of the file that requires monitoring for changes or updates, «
The Visual Studio debugger uses a checksum to make sure that it always finds the right source. The compiler computes the checksum for a source file, and then emits the output to the program database (PDB) file. The debugger then uses the PDB to compare against the checksum that it computes for the source file.
If the compiler doesn’t find a #pragma checksum directive in the file, it computes the checksum and writes the value to the PDB file.
Директивы препроцессора
Препроцессор
Процесс компиляции прошивки очень непростой и имеет несколько этапов, один из первых – работа препроцессора. Препроцессору можно давать команды, которые он выполнит перед компиляцией кода прошивки: это может быть подключение файлов, замена текста, условные конструкции и некоторые другие вещи. Также у препроцессора есть макросы, которые позволяют добавлять в код некоторые интересные вещи.
#include – подключить файл
Также можно указать путь к файлу, который нужно подключить. Например у нас в папке со скетчем есть папка libs, а в ней – файл mylib.h. Чтобы подключить такой файл, пишем:
Компилятор будет искать его в папке со скетчем, в подпапке libs.
#define / undef
Или быстрого и удобного отключения отладки в коде:
Или даже задефайнить целый кусок кода, используя переносы и обратный слэш
Если DEBUG задефайнен, то DEBUG_PRINT – это макро-функция, которая выводит значение в порт. А если не задефайнен – все вызовы DEBUG_PRINT просто убираются из кода и экономят память!
Если DEBUG_ENABLE задефайнен – все вызовы DEBUG() в коде будут заменены на вывод в порт. Если не задефайнен – они будут заменены НИЧЕМ, то есть просто “вырежутся” из кода! Также по DEBUG_ENABLE можно запустить сериал и получить полный контроль над отладкой: если она не нужна – убрали DEBUG_ENABLE и из кода убрался запуск порта и все выводы, что резко сокращает объём занимаемой памяти:
Проблемы
На этом сложности не заканчиваются: #define из одной библиотеки может пролезть в другую библиотеку, которая подключена после первой! Вернёмся к тому же примеру с DarkMagenta – если в моей библиотеке я задефайню это слово и подключу библиотеку до подключения FastLED – я получу ошибку компиляции! Если поменять подключение местами – ошибки не будет. Но, если я захочу использовать DarkMagenta в своём скетче, я буду неприятно удивлён =)
Что я хочу сказать в итоге: #define – гораздо более мощный инструмент, чем может показаться на первый взгляд. Использование define с невнимательным отношением к именам может привести к ошибке, которую будет непросто отловить. Это палка о двух концах: с одной стороны хочется использовать в своей библиотеке define, чтобы никто другой случайно не пролез со своими дефайнами. В то же время, своя библиотека может начать конфликтовать с другими библиотеками. Какой тут выход? Очень простой! Делать имена дефайнов максимально уникальными: если это библиотека – оставлять префикс библиотеки, если это скетч – делать префикс с именем скетча. Также можно отказаться от define в пользу констант или enum, enum кстати удобнее define в плане создания набора констант, а места занимает совсем немного!
#if – условная компиляция
Условная компиляция является весьма мощным инструментом, при помощи которого можно вмешиваться в компиляцию кода и делать его очень универсальным как для пользователя, так и для железа. Рассмотрим директивы условной компиляции:
При помощи условной компиляции можно буквально включать и выключать целые части кода из компиляции, то есть из финальной версии программы, которая будет загружена в микроконтроллер. Рассмотрим несколько конструкция для примера: [fusion_accordion type=”” boxed_mode=”” border_size=”1″ border_color=”” background_color=”” hover_color=”” divider_line=”” title_font_size=”” icon_size=”” icon_color=”” icon_boxed_mode=”” icon_box_color=”” icon_alignment=”” toggle_hover_accent_color=”” hide_on_mobile=”small-visibility,medium-visibility,large-visibility” title=”Пример 1″ open=”no”] [/fusion_toggle][fusion_toggle title=”Пример 2″ open=”no”] [/fusion_toggle][fusion_toggle title=”Пример 3″ open=”no”] [/fusion_toggle][/fusion_accordion]
Сообщения от компилятора
#pragma
Указывает компилятору, что данный файл нужно подключить только один раз. Является более удобной и современной заменой конструкции вида
Такую конструкцию вы можете встретить в 99% библиотек, файлов ядра и вообще заголовочников с кодом.
Конструкция с #pragma pack и #pragma pop позволяет более рационально распределять структуры в памяти. Тема сложная, читайте на Хабре.
Макросы
У препроцессора есть несколько интересных макросов, которыми можно пользоваться в своём коде. Рассмотрим некоторые полезные из них, которые работают на Arduino (точнее, на компиляторе avr-gcc).
__func__ и __FUNCTION__
Макросы __func__ и __FUNCTION__ “возвращают” в виде символьного массива (строки) название функции, внутри которой они вызваны. Являются аналогом друг друга. Например:
__DATE__ и __TIME__
__DATE__ возвращает дату компиляции по системному времени в виде символьного массива (строки) в формате __TIME__ возвращает время компиляции по системному времени в виде символьного массива (строки) в формате ЧЧ:ММ:СС
Работать напрямую с этим макросом очень неудобно, это ведь просто набор символов. У меня есть библиотека buildTime, которая позволяет получать отдельно каждый параметр (день, месяц, год, часы, минуты, секунды). Скачать/почитать можно здесь.
__FILE__ и __BASE_FILE__
__FILE__ и __BASE_FILE__ возвращают полный путь к текущему файлу, опять же как строку. Являются аналогами друг друга.
__LINE__
__LINE__ возвращает номер строки в документе, в которой вызван этот макрос
__COUNTER__
__COUNTER__ возвращает значение, начиная с 0. Значение __COUNTER__ увеличивается на единицу с каждым вызовом макроса в коде.
__COUNTER__ можно использовать для генерации уникальных имён переменных, но об этом мы поговорим когда нибудь в другой раз.