Макросы в cс


Многострочные макросы в Си

В этой статье мы обсудим, как написать многострочный макрос. Мы можем написать многострочный макрос такой же как функция, но каждый оператор заканчивается символом «/». Давайте посмотрим на примере. Ниже приведен простой макрос, который принимает входной номер от пользователя и печатает, является ли введенный номер четным или нечетным.

ссылка на сайт
brightness_4
код

На первый взгляд, код выглядит нормально, но когда мы пытаемся скомпилировать этот код, он выдает ошибку компиляции.

Давайте посмотрим, какую ошибку мы допустили при написании макроса. Мы заключили макрос в фигурные скобки. Согласно правилу C-языка, каждое C-выражение должно заканчиваться точкой с запятой. Вот почему мы завершили MACRO точкой с запятой. Здесь ошибка. Давайте посмотрим, как компиляция расширяет этот макрос.

Мы завершили макрос точкой с запятой. Когда компилятор раскрывает макрос, он ставит точку с запятой после оператора «if». Из-за точки с запятой между «оператором if и else» компилятор выдает ошибку компиляции. Выше программа будет работать нормально, если мы игнорируем «остальное» часть.

Чтобы преодолеть это ограничение, мы можем заключить наш макрос в оператор «do-while (0)». Наш модифицированный макрос будет выглядеть следующим образом.

Макросы в c/с

Макрос это тип сокращения, который можно заранее определить и использовать в дальнейшем. Существует довольно много возможностей, связанных с использованием макросов в С препроцессоре.

«Простой макрос» это тип сокращения. Это идентификатор, который используется для представления фрагмента кода.

Перед использованием макроса его необходимо определить с помощью директивы ‘#define’, за которой следует название макроса и фрагмент кода, который будет идентифицировать этот макрос. Например,

Обычно, макроопределением должна быть отдельная строка, как и при использовании всех директив препроцессора. (Длинное макроопределение можно разбить на несколько строк с применением последовательности backslash-newline.) Хотя существует одно исключение: символы перевода строки могут быть вкючены в макроопределение если они находятся в строковой или символьной константе, потому как макроопределение не может содержать каких-либо специальных символов. Макроопределение автоматически дополняется соответствующим специальным символом, который завершает строчную или символьную константу. Комментарии в макроопределениях могут содержать символы перевода строки, так как это ни на что не влияет, потому как все комментарии полностью заменяются пробелами вне зависимости от того, что они содержат.

В отличие от выше сказанного, не существует никаких ограничений на значение макроса. Скобки не обязательно должны закрываться. Тело макроса не обязательно должно содержать правильный С код.

Препроцессор С обрабатывает программу последовательно, поэтому макроопределения вступают в силу только в местах, где они используются. Поэтому, после обработки следующих данных С препроцессором

Это не является тем же, что и определение макроса ‘TABLESIZE’ равным значению ‘1020’. Директива ‘#define’ для макроса ‘TABLESIZE’ использует в точности те данные, которые были указаны в ее теле и заменяет макрос ‘BUFSIZE’ на его значение.

Значение простого макроса всегда одно и то же при каждом его использовании. Макросы могут быть более гибкими, если они принимают аргументы. Аргументами являются фрагменты кода, которые прилагаются при каждом использовании макроса. Эти фрагменты включаются в расширение макроса в соответствии с указаниями в макроопределении.

Для определения макроса, использующего аргументы, применяется директива ‘#define’ со списком имен аргументов в скобках после имени макроса. Именами аргументов могут быть любые правильные С идентификаторы, разделенные запятыми и, возможно, пробелами. Открывающаяся скобка должна следовать сразу же после имени макроса без каких-либо пробелов.

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

Значение макроса зависит от используемых аргументов. Каждое имя аргумента во всем макроопределении заменяется на значения соответствующих указанных аргументов. При использовании макроса ‘min’, рассмотренного ранее, следующим образом:

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

После подстановки указанных аргументов в тело макроса, полученный в результате текст добавляется к началу оставшихся данных и производится проверка на наличие других вызовов макросов. Поэтому указываемые аргументы могут содержать ссылки к другим макросам как с аргументами, так и без, а также к тому же макросу. Тело макроса также может включать ссылки к другим макросам. Например, макрос ‘min (min (a, b), c)’ заменяется следующим текстом:

Если макрос ‘foo’ принимает один аргумент и нужно передать ему пустой аргумент, то в скобках следует указать по крайней мере один пробел: ‘foo ( )’. Если пробел не указывать, а макрос ‘foo’ требует один аргумент, то произойдет ошибка. Для вызова макроса, не принимающего аргументы, можно использовать конструкцию ‘foo0()’ как рассмотрено ниже:

Подобное двойственное использование одного имени может привести к осложнениям и его следует избегать, за исключением случаев, когда оба значения являются синонимами, то есть когда под одним именем определена функция и макрос и оба выполняют одинаковые действия. Можно рассматривать это имя как имя функции. Использование имени не для ссылки (например, для получения адреса) приведет к вызову функции, в то время как ссылка приведет к замене имени на значение макроса и в результате будет получен более эффективный но идентичный код. Например, используется функция с именем ‘min’ в том же исходном файле, где определен макрос с тем же именем. Если написать ‘&min’ без списка аргументов, то это приведет к вызову функции. Если же написать ‘min (x, bb)’ со списком аргументов, то вместо этого будет произведена замена на значение соответствующего макроса. Если использовать конструкцию ‘(min) (a, bb)’, где за именем ‘min’ не следует открывающаяся скобка, то будет произведен вызов функции ‘min’.

Нельзя определять простой макрос и макрос с аргументами с одним именем.

В определении макроса с аргументами список аргументов должен следовать сразу после имени макроса без пробелов. Если после имени макроса стоит пробел, то макрос определяется без аргументов, а остальная часть строки становится значением макроса. Причиной этому является то, что довольно часто определяются макросы без аргументов. Определение макросов подобным образом позволяет выполнять такие операции как

Некоторые простые макросы являются заранее определенными. Их можно применять без предварительного определения. Они разделяются на два класса: стандартные макросы и системно-зависимые макросы.

Стандартные заранее определенные макросы

Стандартные заранее определенные макросы могут применяться вне зависимости от используемой платформы или операционной системы на которой функционирует GNU C. Их имена начинаются и заканчиваются двойным символом подчеркивания. Все макросы в следующем списке до ‘__GNUC__’ являются стандартизированными ANSI C. Остальные макросы являются расширениями GNU C. ‘__FILE__’

Этот макрос заменяется на имя текущего исходного файла в форме строковой константы С. Возвращаемым именем является одно из указанных в директиве ‘#include’ или имя основного исходного файла.

Этот макрос заменяется на номер текущей строки в форме десятичной целой константы. В то время как он называется заранее определенным макросом, его значение меняется динамически.

Этот макрос и макрос ‘__FILE__’ используются при генерировании сообщения об ошибке для вывода несоответствия, определенного программой. Сообщение может содержать номер строки исходного файла где была обнаружена ошибка. Например,

Значения ‘__FILE__’ и ‘__LINE__’ изменяются при использовании директивы ‘#line’.

Этот макрос заменяется на строчную константу, которая указывает дату запуска препроцессора. Эта константа содержит одинадцать символов и выглядит примерно так ‘»Jan 29 1987″‘ или ‘»Apr 1 1905″‘.

Этот макрос заменяется на строковую константу, которая указывает время запуска препроцессора. Константа содержит восемь символов и выглядит примерно так: ‘»23:59:01:’.

Этот макрос заменяется на константу со значением 1 для указания, что это С стандарта ANSI.

Этот макрос заменяется на номер версии стандарта С, длинной целой константой в форме ‘YYYYMML’, где YYYY и MM год и месяц выхода версии стандарта. Это указывает на версию стандарта С, к которой относится препроцессор.

Этот макрос определен тогда и только тогда, когда используется GNU C. Он определен только тогда используется полный GNU C компилятор. Если вызвать препроцессор отдельно, то этот макрос будет не определен. Его значение указывает на основной номер версии GNU CC (‘1’ для версии 1 GNU CC, которая уже является устаревшей, и ‘2’ для версии 2).

Этот макрос содержит дополнительный номер версии компилятора. Он может быть использован при работе с отличительными возможностями различных выпусков компилятора.

Компилятор GNU C определяет этот макрос если компилируемым языком является С++.

Стандарт ANSI для С++ раньше требовал определения этой переменной. Хотя ее наличие больше не требуется, в GNU C++ она все еще определяется, как и в других известных компиляторах С++. Этот макрос может быть использован для определения каким компилятором был скомпилирован заголовок (С или С++).

Этот макрос определяется тогда и только тогда, когда при вызове GNU C указывается опция ‘-ansi’. Он определяется как пустая строка.

Этот макрос заменяется на имя основного исходного файла в форме строковой константы С. Это исходный файл, указываемый в качестве параметра при вызове компилятора С.

Этот макрос заменяется на десятичную целую константу, которая указывает на уровень вложенности подключаемых файлов. Его значение увеличивается на единицу при обработке директивы ‘#include’ и уменьшается на единицу при завершении обработки каждого файла. Начальное значение для файлов, указываемых в командной строке при вызове компилятора является равным нулю.

Этот макрос заменяется сторокой, указывающей номер версии GNU C. Обычно это последовательность десятичных чисел, разделенных точками. Например ‘»2.6.0″‘.

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

Этот макрос определяется тогда и только тогда, когда тип данных ‘char’ является беззнаковым. Он реализован для правильного функционирования подключаемого файла ‘limit.h’. Не следует использовать этот макрос. Вместо этого можно использовать стандартные макросы, определенные в файле ‘limit.h’. Препроцессор использует этот макрос для определения необходимости в добавлении знакового бита в больших восьмеричных символьных константах.

Этот макрос заменяется на сроку, описывающую префикс, добавляемый к обозначению регистров процессора в ассемблерном коде. Он может использоваться для написания ассемблерного кода, функционирующего в различных оболочках. Например, в оболочке ‘m68k-aout’ производится замена на строку ‘»»‘, а в оболочке ‘m68k-coff’ макрос заменяется на строку ‘»%»‘.

Этот макрос заменяется на строку, описывающую префикс, добавляемый к меткам пользователя в ассемблерном коде. Он может использоваться для написания ассемблерного кода, функционирующего в различных оболочках. Например, в оболочке ‘m68k-out’ он заменяется на строку ‘» «‘, а в оболочке ‘m68k-coff’ — на строку ‘»»‘.

Нестандартные заранее определенные макросы

Обычно С препроцессор имеет несколько заранее определенных макросов, значения которых различаются в зависимости от используемой платформы и операционной системы. В данном руководстве не представляется возможным рассмотреть все макросы. Здесь описаны только наиболее типичные из них. Для просмотра значений заранее определенных макросов можно воспользоваться командой ‘cpp -dM’.

Некоторые нестандартные заранее определенные макросы более или менее подробно описывают тип используемой операционной системы. Например, ‘unix’

Этот макрос обычно определен на всех системах Unix.

Этот макрос определен на последних версиях системы Berkley Unix (возможно только в версии 4.3).

Другие макросы описывают тип центрального процесора. Например,

Определен на Vax компьютерах.

Определен на большинстве компьютеров, использующих процессор Motorola 68000, 68010 или 68020.

Также определен на большинстве компьютеров с процессором 68000, 68010 или 68020. Хотя некоторые разработчики используют ‘mc68000’, а некоторые — ‘m68k’. Некоторые заранее определяют оба макроса.

Определен на некоторых системах с процессором 68020 в дополнение к макросам ‘mc68000’ и ‘m68k’, которые являются менее специфичными.

Определены на компьютерах с процессорами из семейства AMD 29000.

Определен на компьютерах, использующих процессоры серии National Semiconductor 32000.

Другие нестандартные макросы описывают изготовителей компьютерных систем. Например,

Определен на всех моделях компьютеров Sun.

Определен на всех моделях компьютеров Pyramid.

Определен на всех моделях компьютеров Sequent. Эти заранее определенные символы являются не только нестандартными, но они к тому же не соответствуют стандарту ANSI, потому что их имена не начинаются с символа подчеркивания. Поэтому опция ‘-ansi’ запрещает определение этих символов.

Это приводит к тому, что опция ‘-ansi’ становится бесполезной, так как большое количество программ зависит от нестандартных заранее определенных символов. Даже системные подключамые файлы проверяют их значения и генерируют неправильные объявления в случае если требуемые имена не определены. В результате очень мало программ компилируется с опцией ‘-ansi’.

Что же нужно сделать в ANSI C программе для того, чтобы проверить тип используемого компьютера?

Для этой цели GNU C предоставляет параллельную серию символов, имена которых состоят из обычных символов с добавлением строки ‘__’ с начала и с конца. Таким образом символ ‘__vax__’ используется на системах Vax, и так далее.

Набор нестандартных заранее определенных символов в GNU C препроцессоре изменяется (при компиляции самого компилятора) с помощью макроса ‘CPP_PREDEFINES’, которым является строка, состоящая из опций ‘-D’, разделенных пробелами. Например, на системе Sun 3 используется следующее макроопределение:

«Стрингификация» означает преобразование фрагмента кода в строковую константу, которая содержит текст этого фрагмента кода. Например, в результате стрингификации ‘foo (z)’ получается ‘»foo (z)»‘.

В С препроцессоре, стрингификация является опцией, используемой при замене аргументов в макросе макроопределением. При появлении имени аргумента в теле макроопределения, символ ‘#’ перед именем аргумента указывает на стрингификацию соответствующего аргумента при его подстановке в этом месте макроопределения. Этот же аргумент может быть заменен в другом месте макроопределения без его стрингификации, если перед именем аргумента нет символа ‘#’.

Вот пример макроопределения с использованием стрингификации:

Возможности срингификации ограничены до преобразования одного макро аргумента в одну строковую константу: не существует методов комбинирования аргумента с другим текстом и посследующей стрингификации полученных данных. Хотя рассмотренный выше пример показывает как может быть достигнут подобный результат в стандартном ANSI C с использованием возможности объединения смежных строковых констант в одну. Препроцессор стрингифицирует реальное значение ‘EXP’ в отдельную строковую константу и в результате получается следующий текст:

Пробелы (включая комментарии), находящиеся в тексте, обрабатываются в соответствии с установленными правилами. Все предшествующие и последующие пробелы игнорируются. Любые последовательности пробелов в середине текста в результате обработки заменяются на отдельный пробел.

Илон Маск рекомендует:  Что такое код imagesise

«Объединение» означает соединение двух строковых констант в одну. При работе с макросами, это означает объединение двух лексических единиц в одну более длинную. Один аргумент макроса может быть объединен с другим аргументом или с каким-либо текстом. Полученное значение может быть именем функции, переменной или типа, а также ключевым словом С. Оно даже может быть именем другого макроса.

При определении макроса, проверяется наличие операторов ‘##’ в его теле. При вызове макроса и после подстановки аргументов все операторы ‘##’, а также все пробелы рядом с ними (включая пробелы, принадлежащие аргументам) удаляются. В результате производится объединение синтаксических конструкций с обоих сторон оператора ‘##’.

Рассмотрим С программу, интерпретирующую указываемые команды. Для этого должна существовать таблица команд, возможно массив из структур, описанный следующим образом:

Следует заметить, что препроцессор С преобразует все комментарии в пробелы перед обработкой макросов. Поэтому нельзя создать комментарий путем объединения ‘/’ и ‘*’ так как последовательность символов ‘/*’ не является лексической конструкцией. Также можно использовать комментарии в макроопределениях после строки ‘##’ или в объединяемых аргументах, так как сначала комментарии заменяются на пробелы, а при объединении эти пробелы игнорируются.

«Удалить» макрос означает отменить его определение. Это производится с помощью директивы ‘#undef’, за которой следует имя макроса.

Как и определение, удаление макросов появляется в определенном месте исходного файла и вступает в силу с этого места. Например,

Директива ‘#undef’ используется в такой же форме и для отмены макроопределений с аргументами или без них. Применение этой директивы к неопределенному макросу не дает никакого эффекта.

«Переопределение» макроса означает определение (с помощью директивы ‘#include’) имени, которое уже было определено как макрос.

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

Нетривиальные переопределения рассматриваются как возможная ошибка, поэтому в таких случаях препроцессор выдает предупреждающее сообщение. Однако, это иногда помогает при изменении определения макроса во время предварительной компиляции. Появление предупреждающего сообщения можно запретить путем предварительного уничтожения макроса с помощью директивы ‘#undef’.

Для простого переопределения новое определение долно точно совпадать с предыдущим значением за исключением двух случаев:

В начале и в конце определения могут быть добавлены или удалены пробелы.

Пробелы можно изменять в середине определения (но не в середине строки). Однако они не могут быть полностью удалены, а также не могут быть вставлены туда, где их не было вообще.

В этом разделе рассматриваются некоторые специальные правила работы, связанные с макросами и макроподстановками, а также указываются отдельные случаи, которые следует иметь в виду.

Неправильно используемые конструкции

При вызове макроса с аргументами, они подставляются в тело макроса, а затем просматриваются полученные после подстановки данные вместе с оставшейся частью исходного файла на предмет дополнительных макро вызовов.

Возможно объединение макро вызова, исходящего частично от тела макроса и частично — от аргументов. Например,

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

Нестандартная группировка арифметических выражений

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

Допустим, существует следующее макроопределение:

Однако, нестандартная группировка может привести к другому результату. Рассмотрим выражение ‘sizeof ceil_div(1, 2)’. Здесь используется выражение С, вычисляющее размер типа данных ‘ceil_div(1, 2)’, но в действительности производятся совсем иные действия. В данном случае указанная срока заменяется на следующую:

Заключение в скобки всего макроопределения позволяет избежать подобных проблем. Далее дан правильный пример определения макроса ‘ceil_div’.


Использование точки с запятой

Иногда требуется определять макросы, используемые в составных конструкциях. Рассмотрим следующий макрос, который использует указатель (аргумент ‘p’ указывает его местоположение):

Вызов этого макроса может выглядеть так: ‘SKIP_SPACES (p, lim)’. Грубо говоря, при его вызове он заменяется на составную конструкцию, которая является полностью законченной и нет необходимости в использовании точки с запятой для ее завершения. Но вызов этого макроса выглядит как вызов функции. Поэтому удобнее будет вызывать этот макрос следующим образом: ‘SKIP_SPACES (p, lim);’

Но это может привести к некоторым трудностям при использовании его перед выражением ‘else’, так как точка с запятой является пустым выражением. Рассмотрим такой пример:

Определение макроса ‘SKIP_SPACES’ может быть изменено для устранения этого недостатка с использованием конструкции ‘do . while’.

Удвоение побочных эффектов

Во многих С программах определяется макрос ‘min’ для вычисления минимума:

Функция ‘foo’ используется в этой конструкции только один раз, в то время как выражение ‘foo (z)’ используется дважды в макроподстановке. В результате функция ‘foo’ может быть вызвана дважды при выполнении выражения. Если в макросе имеются побочные эффекты или для вычисления значений аргументов требуется много времени, результат может быть неожиданным. В данном случае макрос ‘min’ является ненадежным.

Наилучшим решением этой проблемы является определение макроса ‘min’ таким образом, что значение ‘foo (z)’ будет вычисляться только один раз. В языке С нет стандартных средств для выполнения подобных задач, но с использованием расширений GNU C это может быть выполнено следующим образом:

Рекурсивные макросы

«Рекурсивные» макросы — это макросы, в определении которых используется имя самого макроса. Стандарт ANSI C не рассатривает рекурсивный вызов макроса как вызов. Он поступает на вывод препроцессора без изменений.

Следуя обычным правилам, каждая ссылка на ‘foo’ заменяется на значение ‘(4 + foo)’, затем это значение просматривается еще раз и заменяется на ‘(4 + (4 + foo))’ и так далее, пока это не приведет к ошибке (memory full) препроцессора.

Однако, правило об использовании рекурсивных макросов завершит этот процесс после получения результата ‘(4 + foo)’. Поэтому этот макрос может использоваться для прибавления 4 к значению переменной ‘foo’.

В большинстве случаев не следует опираться на эту возможность. При чтении исходных текстов может возникнуть путаница между тем, какое значение является переменной, а какое — вызовом макроса.

Также используется специальное правило для «косвенной» рекурсии. Здесь имеется в виду случай, когда макрос X заменяется на значение ‘y’, которое является макросом и заменяется на значение ‘x’. В результате ссылка на макрос ‘x’ является косвенной и происходит от подстановки макроса ‘x’, таким образом, это является рекурсией и далее не обрабатывается. Поэтому после обработки

Но предположим, что ‘y’ используется где-либо еще и не в определении макроса ‘x’. Поэтому использование значения ‘x’ в подстановке макроса ‘y’ не является рекурсией. Таким образом, производится подстановка. Однако, подстановка ‘x’ содержит ссылку на ‘y’, а это является косвенной рекурсией. В результате ‘y’ заменяется на ‘(2 * (4 + y))’.

Неизвестно где такие возможности могут быть использованы, но это определено стандартом ANSI C.

Отдельная подстановка макро аргументов

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

Что же происходит на самом деле, является довольно тонким моментом. Сначала значения каждого аргумента проверяются на наличие макро вызовов. Затем полученные значения подставляются в тело макроса и полученная макро подстановка проверяется еще раз на наличие новых макросов.

В результате значения макроаргументов проверяются дважды.

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

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

Первый проход не выполняется, если аргумент образован путем стрингификации или объединения. Поэтому

При стрингификации и объединении аргумент используется в таком виде, в каком он был указан без последующего просмотра его значения. Этот же аргумент может быть просмотрен, если он указан где-либо еще без использования стрингификации или объединения.

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

  • При однородных вызовах макросов.
  • При использовании макросов, вызывающих другие макросы, которые используют стрингификацию или объединение.
  • При использовании макросов, содержащих открытые запятые.

Макро вызовы называются «однородными», если аргумент этого макроса содержит вызов этого же макроса. Например, ‘f’ это макрос, принимающий один аргумент, а ‘f (f (1))’ является однородной парой вызовов макроса ‘f’. Требуемая подстановка производится путем подстановки значения ‘f (1)’ и его замены на определение ‘f’. Дополнительный проход приводит к желаемому результату. Без его выполнения значение ‘f (1)’ будет заменено как аргумент и во втором проходе оно не будет заменено, так как будет является рекурсивным элементом. Таким образом, применение второго прохода предотвращает нежелательный побочный эффект правила о рекурсивных макросах.

Но применение второго прохода приводит к некоторым осложнениям в отдельных случаях при вызовах однородных макросов. Рассмотрим пример:

Зависимые макросы

«Зависимым» макросом называется макрос, тело которого содержит ссылку на другой макрос. Это довольно часто используется. Например,

Подстановка значения ‘TABLESIZE’ производится только при использовании этого макроса.

При изменении значения ‘BUFSIZE’ в каком-либо месте программы ее выполнение меняется. Макрос ‘TABLESIZE’, определенный как было описано выше, всегда заменяется с использованием значения макроса ‘BUFSIZE’:

При обычной обработке макросов все символы newline в макроаргументах используются при макроподстановке. Это означает, что если некоторые аргументы подставляются более одного раза или вообще не подставляются, то символы newline могут дублироваться. Если подстановка состоит из нескольких конструкций, то в результате порядок строк этих конструкций будет нарушен. Это может привести к неправильным значениям номеров строк в сообщениях об ошибках или при работе с отладчиком.

При работе GNU C препроцессора в режиме ANSI C, им контролируется многократное использование одного аргумента. При первом его использовании подставляются все символы newline, а при последующем использовании эти символы игнорируются. Но даже при работе в таком режиме может возникнуть ошибочная нумерация строк если аргументы используются не в надлежащем порядке или вообще не используются.

Создание макрос-функции в C++

Задача стоит написать функцию-макрос. Сама функция написано и даже один раз корректно срабатывает. Вот она:

Но при повторном вызове компилятор выдает ошибки вида:

Помогите разобраться в причине, пожалуйста

6 ответов 6

Прежде всего мне представляется, что вы имели в виду что-то подобное:

Предвидя ваш вопрос, почему используется конструкция do-while , скажу, чтобы, например, этот макрос можно было бы использовать в if-else предложении:

Ниже представлена демонстрационная программа

Ее вывод на консоль:

Что касается вашего определения макроса, то он постоянно определяет переменные aa и bb в той области видимости, где макрос вызывается.

Поэтому компилятор выдает сообщение об ошибке, что эти переменные были уже определены, если макрос вызывается более одного раза.

Использование макросов в C++ — это анахронизм. Лучше использовать встраиваемые функции, то есть функции со спецификатором inline . Более того в C++ уже есть стандартная функция std::max . Поэтому вы могли бы просто написать

Имейте в виду, что поведение вашего макроса отличается от поведения аналогичной стандартной функции std::max . Ваш макрос в случае равенства x и y возвращает y , тогда как стандартная функция std::max возвращает x . И кроме того вы используете оператор > тогда как стандартная функция использует оператор . Для фундаментальных типов это может не играть существенной роли, тогда как для определенных пользователем типов это имеет важное значение.

А самое главное — ваш макрос нельзя использовать в выражениях. Это ограничивает его применение.

Препроцессор C

Как работает препроцессор.

Для понимания работы препроцессора важно осознавать уровень абстракций с которыми он работает. Основным понятием в препроцессоре является токен (token) — это, грубо говоря последовательность символов, отделённая разделителями, похоже на идентификатор в Си, но значительно шире. В общем в препроцессоре есть только директивы, токены, строковые и числовые литералы и выражения, еще он понимает комментарии (просто их игнорирует). Упрощенно говоря, препроцессор работает с текстовыми строчками, умеет их склеивать, превращать в строковый литерал, выполнять макроподстановку, и подключать файлы.

Директивы препроцессора.

Директивы — это специальные команды, которые препроцессор распознаёт и выполняет. Все директивы начинаются со знака #. Если первый непробельный символ в строке это — #, то препроцессор будет пытаться распознать в ней свою директиву.
Существуют следующие директивы:
— Подключение файлов: #include, #include_next.
— Условная компиляция: #if, #ifdef, #ifndef, #else, #elif and #endif.
— Диагностика: #error, #warning, #line.
— Дополнительная информация компилятору: #pragma
— Макроопределения: #define

Подключение файлов.

Первая директива, которая всем встречается при изучении языка Си — это #include. Записывается так:

Встретив в исходнике эту директиву, препроцессор заменяет её на содержимое файла, имя которого указанно в параметре. Различие между первой и второй формой записи состоит в том, где в первую очередь, препроцессор будет искать указанный файл. В первом случае он сначала будет искать в каталогах с системными заголовками. Во втором — в том-же каталоге, где находится компилируемый исходник. Грубо говоря, при подключении системных/стандартных заголовков нужно имя файла писать в угловых скобках, а для своих — в кавычках.
Мало кто знает, но есть ещё одна директива для включения файлов — #include_next. Записывается она также как и обычный #include, но ее поведение несколько отличается. Дело в том, что препроцессор ищет подключаемые заголовки по многим путям, и бывает, что искомый файл есть сразу в нескольких каталогах. В случае применения директивы #include, он подключает первый попавшийся файл с совпавшим именем. В случае #include_next — будет подключен первый файл с совпавшим именем, который еще не включался в эту единицу трансляции, то есть следующий еще не подключенный. Причем применять #include_next можно только в этих самых заголовках с совпадающими именами, применённая в.с файле эта директива ведёт себя как обычный #include. Таким образом, если в каждом из заголовков с одинаковыми именами применить #include_next, то конечном итоге, они все будут подключены.
Ещё одна интересная особенность директивы #include то, что в ней тоже выполняется макроподстановка. То есть, параметра в ней можно использовать любой макрос, который развернётся в имя файла в одной из двух допустимых форм(в кавычках или в угловых скобках). Например:

В данном случае макрос в директиве include развернётся в #include . Для склеивания частей пути к файлу использован трюк с пустым комментарием. Таким образом можно, например, выбирать каталог для подключения системно-зависимых заголовков. Хотя пример несколько надуманный и выбирать каталоги для системно-зависимых заголовков лучше в системе сборки проекта через соответствующие ключи компилятору. Еще эту технику можно использовать для того, чтобы подключить в определенном месте заголовок, имя которого определено где-то далеко, например в недрах какой-то библиотеки.

Условная компиляция

Применяется, когда в зависимости от значения различных макросов, нужно компилировать, или нет, тот или иной кусок кода, или установить другие макросы.

Где условие — это выражение препроцессора. Это может быть любая комбинация макросов, условий и целочисленных литералов, которая в результате макроподстановки превратится в выражение состоящее только из целочисленных литералов, арифметических операций и логических операторов. Так-же здесь ещё можно использовать единственный «макрооператор» — defined — он превращается в 1, если его операнд определён, и 0 — если нет.

__AVR__ и __ICCAVR__ — это специальные предопределённый макросы, позволяющие определить используемый компилятор. Соответственно для каждого компилятора существует предопределённый макрос, который позволяет его однозначно идентифицировать.
Как уже говорилось, препроцессор работает на уровне отдельных токенов — текстовых строчек, их значение препроцессору безразлично, и он ничего не знает о правилах и грамматике целевого языка. Поэтому в директивах условной компиляции нельзя использовать никакие конструкции языка Си. Например:

В обоих приведённых примерах условия будут всегда ложны и содержимое #if блоков не выполнится. Препроцессор не знает ничего, ни о структурах и их размере, ни о переменных — они-ж не макросы. По этому в первом случае нужно использовать static_assert, реализованный средствами самого Си. А вот во втором случае можно извернутся так:

Условия могут быть сложными и содержать в себе макросы, которые будут полностью развёрнуты перед вычислением условия:

В данном примере блок #if выполнится если макрос BUFFER_SIZE имеет значение кратное степени двойки и если определен макрос OPTIMIZE_FOR_POWER_OF_2. Конструкция IS_POWER_OF_2(BUFFER_SIZE) после макроподстановки развернется в выражение ((16) & (16)-1 == 0), которое препроцессор легко вычислит.
Для конструкции типа #if defined есть сокращенная форма: #ifdef. Она во всём эквивалентна полной форме, за исключением того, что в сокращенной форме нельзя комбинировать несколько условий.
Также директивы условной компиляции часто используются для предотвращения повторного включения заголовочных файлов (include guard):

Эта конструкция гарантирует, что все определения из заголовка будут включены только один раз в единицу трансляции.

Диагностика.

В предыдущих примерах мы уже встретились с одной диагностической директивой — #error. Назначение её предельно просто — остановить компиляцию с сообщением об ошибке, указанном после директивы. Её можно использовать совместно с директивами условной компиляции для того, чтоб убедиться установлен ли какой-то важный макрос и, что он имеет правильное значение.
Также существует директива #warning, аналогична #error, но не прерывает компиляцию, а выдаёт предупреждение.
Директива #line служит для задания номеров строк и имени файла, показываемых в сообщениях об ошибках и возвращаемые специальными макросами __LINE__ и __FILE__. Например:

При этом в сообщениях об ошибках мы увидим следущее:

Надо учитывать, что такая конструкция собьёт столку любую IDE (и человека тоже) и найти место ошибки будет очень не просто. Однако этот трюк можно использовать, чтоб указать на ошибку, возникшую где-то далеко от места, где мы ее проверяем, например на какой-то важный макрос, определённый в другом файле и имеющий не правильное значение. Надо только точно знать где он расположен.

#pragma

Эта директива используется в основном для передачи компилятору различных нестандартных опций, таких как уровни оптимизаций для отдельных частей программы, выравнивания структур, параметров компоновщика и много чего ещё. У каждого компилятора свой набор директив pragma, но есть и стандартные, поддерживаемые многими компиляторами. Например, директива #pragma once . Это замена для include guard, встретив в заголовке эту директиву, препроцессор предотвратит повторное включение этого заголовка в обрабатываемую единицу трансляции.

Макроопределения

Теперь переходим к интересному, собственно к макросам. существуют два типа макросов: макрос-объект(object-like macro) и макрос-функция(function-like macro), оба типа объявляются с помощью директивы #define. Рассмотрим сначала макросы-объекты. Объявляются они как:
#define ИМЯ_МАКРОСА [замещающий текст]
Всё, что идёт после имя макроса до конца строки является замещающим текстом.

При дальнейшей обработке файла, если препроцессор находит имя макроса, он заменяет его на соответствующий замещающий текст — это называется макроподстановка. Если в замещающем тексте макроса встречаются имена других макросов, препроцессор выполнит макроподстановку для каждого из них, и так далее, пока не будут развёрнуты все известные на данный момент макросы.

Когда препроцессор будет обрaбатывать строчку:
char buffer[DOUBLE_BUFFER];
Сначала будет выполнена первая макроподстановка и токен DOUBLE_BUFFER будет заменен на EXTRA_BUFFER * 2. Тут-же будет выполнена вторая макроподстановка и токен EXTRA_BUFFER заменется на (BUFFER_SIZE +10), потом BUFFER_SIZE заменется на 32. В результате вся строчка после препроцессинга будет выглядеть так:

Здесь становится понятно, зачем были нужны скобки в макросе EXTRA_BUFFER, без них результирующее выражение получилось бы таким:

А это явно не то, что мы хотели получить. Отсюда правило:
Если макрос содержит какое-то выражение, то оно обязательно должно быть заключено в скобки, иначе могут происходить всякие неочевидные вещи.
Также важно понимать, что препроцессор сам ничего не вычисляет (кроме как в условных директивах #if), он просто склеивает текстовые строчки.
А что будет, если какой-то макрос будет ссылаться сам на себя, непосредственно, и косвенно через другие макросы? Ничего не будет, рекурсии не получится, как только препроцессор просечет рекурсию, он прекратит макроподстановку макроса её вызвавшего и оставит его как есть. Например:

При этом будет определён и символ препроцессора flags и переменная flags. такую особенность часто используют для того, чтобы иметь возможность проверить наличие переменной(или любого другого идентификатора) с помощью директив условной компиляции #ifdef/#else/#endif:
// если флаги определены

Хотя я бы не рекомендовал использовать такой приём без крайней необходимости, так как он сильно затрудняет понимание программы и чреват ошибками, поскольку мы по сути пишем две версии программы в одном наборе исходников со всеми вытекающими последствиями. Ведь есть-же системы контроля версий!

Предопределённые макросы

У каждого компилятора есть множество предопределённых макросов, есть стандартные — общие для всех: gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html#Standard-Predefined-Macros
Есть специфичные для каждого отдельного компилятора, например у gcc:
gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros
И даже для каждой поддерживаемой платформы, например для avr-gcc, список всех предопределённых макросов(кроме контекстно зависимых, таких как __LINE__ и т.д) можно получить набрав в командной строке:

Соответственно, вместо atmega16 писать интересующий контроллер.
В других компиляторах предопределённые макросы ищутся в соответствующей документации.
Все эти макросы могут использоваться для определения платформы, для которой компилируется программа, используемого языка (Си, Си++ или ассемблер) и различных особенностей целевой архитектуры.
Также есть макросы предназначенные в основном для отладки: __FILE__, __LINE__ и __FUNCTION__. __FILE__ разворачивается в строковый литерал, содержащий имя обрабатываемого файла. __LINE__ — целочисленный литерал означающий номер текущей строки. __FUNCTION__ — имя текущей функции. Надо заметить, что макрос __FUNCTION__ разворачивается всё-таки не препроцессором а компилятором — препроцессор ничего не знает о функциях в языке Си. Также надо учитывать, что значения __LINE__ и __FILE__ могут изменяться с помощью директивы #line.
Типичное использование макросов __LINE__, __FILE__ и __FUNCTION__:

При этом вызов функции MyError превратится во что-то такое:

Макросы-функции

Второй вид макросов — это макро-функции (function-like macros). Определяются они с помощью той-же директивы #define, после которой (сразу без пробелов) в круглых скобках идёт список разделённых запятыми аргументов:

Макрос SQR предназначен вычислять квадрат переданного ему выражения, в приведённом примере SQR(b) развернётся в (b * b). Вроде-бы нормально, но если этому макросу передать более сложное выражение
,
то он развернётся совсем не в то, что нужно:

Очевидно, что умножение выполнится первым и это у нас уже далеко не квадрат.
Поэтому все аргументы макросов используемые в математических и не только выражениях надо обязательно заключать в скобки:

Однако и этот вариант не свободен от недостатков, например:

Переменная b будет инкрементирована два раза. И у этого недостатка есть решения гибкие и не очень, стандартные и нет, но о них говорить не будем. В данном примере гораздо лучше применить встраиваемую (inline) функцию, она свободна от недостатков макросов:

У макросов-функций есть интересная особенность — макроподстановка в них выполняется два раза. Первый раз — для каждого из параметров до того, как они будут подставлены в тело макроса. Второй раз — для всего тела макроса после подстановки в него параметров. В большинстве случаев это не имеет особого значения. Подробнее об этом можно прочитать здесь:
gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html#Argument-Prescan

В макро-функциях можно использовать два специальных макро-оператора: # и ##. Первый превращает свой операнд в строковый литерал:

Вызов PRINT_VAR в данном случае превратится в

При этом будет напечатана строка: my_var = 10. Здесь для склеивания форматной строки использован тот факт, что две строки разделённые лишь пробельными символами компилятор считает одной строкой: «%s = %» «d».
Макро-оператор ## склеивает два токена в один токен, для которого после будет выполнена макроподстановка:

Применять эти макро-операторы можно только к параметрам макросов. Причем для параметров к которым они применены макроподстановка будет применена только один раз — для полученного результата. То есть параметр PORT_LETTER не будет отдельно сканироваться на наличие в нем макросов. Почему макрос SET_PIN состоит из двух уровней объясняется ниже.
Теперь, допустим, нам нужен макрос, который склеивает идентификатор из двух кусков:

Если параметра этого макроса непосредственно, те токены, что нам нужно склеить, как в примере выше, то всё сработает как надо. Если-же это макросы, которые сначала нужно раскрыть, то придется вводить еще один вспомогательный макрос, который сначала развернёт параметры и передаст их следующему макросу:

Из-за того, что для параметров, для которых применена конкатенация, не производится макроподстановка, в препроцессорном метапрограммировании часто приходится применять такие двухуровневые макросы: один — для развёртывания параметров, второй — делает непосредственную работу.


Макро-функции можно передать имя другой макро-функции в качестве параметра и, соответственно, вызвать её:

Практический пример препроцессорного метапрограммирования

В качестве примера рассмотрим генерацию таблицы для вычисления контрольной суммы CRC16. Функция для вычисления CRC16 для каждого байта выглядит так:

Где newchar — очередной байт сообщения для которого вычисляем CRC,
crcval — предыдущее значение CRC.
сrcTable — таблица из 256 значений, которую нам надо сгенерировать.
Функция возвращает новое значение контрольной суммы.

Первоначальная идея была и вовсе вычислять CRC16 от строкового литерала с помощью препроцессора, чтобы можно было реализоват «switch» по CRC16 от строки, с удобочитаемыми метками. Но только на препроцессоре это сделать не получилось из-за степенной сложности генерируемых выражений — компилятору банально не хватает памяти, чтоб посчитать таким образом CRC16 для двух символов. На шаблонах С++ это можно сделать без проблем.

Елементы таблицы сrcTable можно вычислить с помощью такой функции:

Где v — индекс в таблице,
polynom — полином контрольной суммы, в данном примере будем использовать значение 0x8408, соответствующее стандарту CRC-CCITT.

Теперь нужно этот алгоритм реализовать с помощью препроцессора. Как быть с циклом? В препроцессоре нет ни циклов ни рекурсии. Прийдётся цикл развернуть вручную:

Теперь, вызывая макрос CRC_TABLE_8 мы получаем константное выражение для одного элемента таблицы. Выражение это, кстати, очень длинное порядка 200-400 тысяч символов! Это происходит потому, что каждый(кроме первого) макрос CRC_TABLE_x вызывает 3 макроса более нижнего уровня, а ведь препроцессор сам выражения не вычисляет, оставляя это компилятору. И получается в результате длинна такого выражения порядка 3 в восьмой степени помножить на длинны выражения низшего уровня. Но ничего, это компилятор еще прожевывает. Теперь нужно сгенерировать саму таблицу:
#define CRC_POLYNOM 0x8408

Можно, конечно оставить и так, но есть решение получше, называется — библиотека Boost preprocessor. В ней имеется много всяких полезняшек, в частности есть макрос BOOST_PP_REPEAT, который повторяет заданное количество раз макрос, переданный ему в качестве параметра. С использованием BOOST_PP_REPEAT геерацию таблицы можно написать так:

Выглядит уже вполне неплохо. Макрос, который будет повторяться в BOOST_PP_REPEAT, должен иметь три параметра. Первый уровень вложенности повторения, если мы будем использовать вложенные повторения, мы его не используем. Второй — счётчик, текущая итерация — индекс в нашей таблице. Третий — дополнительный параметр, мы в нем передаём полином контрольной суммы.

Как-же работает BOOST_PP_REPEAT, если в перпроцессоре нет ни циклов, ни рекурсии. Очень просто — определено 256 макросов с именами типа BOOST_PP_REPEAT_x, где х — номер итерации, которые вызывают друг друга по цепочке. В макросе BOOST_PP_REPEAT склеивается имя макроса этой цепочки из токена BOOST_PP_REPEAT_ и количества требуемых повторений. Это несколько упрощенное объяснение, в реальности там чуть сложнее, но основной принцип такой.

Макросы в c/с

Всем привет.
После того как yeputons разочаровал меня сказав, что эту задачу нельзя решить адекватным способом, и дал ссылку на полезные данные по макросам, я решил, что этим всем нужно поделиться со всеми.
Значит приступим.
Для тех, кто не в курсе, макрос — это фрагмент кода, которому дается имя. Когда это имя используется — оно заменяется на содержание макроса. Есть два типа макросов: обычные и функциональные(на английском они называются object-like и function-like, но я бы назвал их именно так).
Обычный макрос — это простой идентификатор, который будет заменен фрагментом кода. В основном используется как константа, или чтобы дать имя списку чисел и/или константным значениям других типов.
Пример 1.
#define MAXN 1000
Теперь всегда когда мы будем писать MAXN будет использоваться число 1000 .
Пример 2.
#define nums 1, 2, 3 Теперь вместо слова nums будет подставляться 1, 2, 3
Пример 3.
#define mc my_class_with_very_very_long_name
Теперь вместо написания иногда очень долгих имен типов мы просто напишем mc .
Функциональный макрос — который будет использоваться как функция. Имеет очень много возможностей.
Пример 4.
#define abs(x) ((x)>=0?(x):-(x))
Теперь мы можем использовать функцию abs(x), при чем x может быть любого типа для которого будет работать сравнение x>=0 .
Пример 5.
#define max(a,b) ((a)>(b)?(a):(b))
Такой макрос можно спокойно (ну почти спокойно. См. Предупреждение 3.) использовать для нахождения максимума, и как и в прошлом примере для корректности работы необходимо, чтобы выражение (a)>(b) имело смысл.
Предостережение 1.
Все переменные имеет смысл, а иногда даже нужно, брать во скобки, чтобы избежать проблем со старшинством операций. Например, если в качестве аргумента пойдет выражение с битовыми операциями.

Макросы могут вызывать друг друга, и в отличии от функций макрос может вызывать макрос, который описан позже него. Но с рекурсией макросы не дружат — ни с прямой, ни с непрямой.
Перевод имени переменной в строку (англ. stringification. Кто-то может перевести это слово?). Можно получить имя переменной как строку.
Пример 6.
#define id(x) #x
Тут я немного о ней рассуждал. Единственное скажу — поистине магическая штука.
Конкатенация строки к имени переменной. Сразу перейдем к примеру.
Пример 7.
#define get(name) (get_##name())
Очень полезно в том же самом ООП, когда методов геттеров много. Хотя по сути жизнь и без этого прекрасна.
Очень много стандартных макросов в С++. Думаю очень много внимания этому уделять не стоит. Кто хочет — почитайте тут.
Переопределение и удаление макросов. Переопределение производится новым вызовом директивы #define name , где name имя уже используемого макроса. Макрос можно переопределить в обычный или функциональный независимо от того, каким он был до этого. Удаление макроса используется при помощи директивы #undef name , где name имя макроса.
Пример 8.
#define func 2 — обычный макрос, который возвращает 2
#undef func — теперь func это обычное имя переменной
#define func 5 — снова обычный макрос
#define func(x) ((x)+5) — переопределение в функциональный
Макрос может быть переопределен даже во время его использования.
Пример 9.

Такой код спокойно возвратит 6. Компилятор при этом будет молчать.
Макросы с переменным числом аргументов. Принцип работы примерно такой же, как и в функциях и/или шаблонах с переменным числом аргументов. Примеры тут. От себя, к сожалению, ничего не добавлю.
Предостережение 2.
Не желательно передавать аргументами макроса функции. Лучше передать ее результат. Ибо если та переменная в макросе используется больше раза — то функция вызовется очень много раз.
Пример 10.
Вспомним макрос max и попробуем запустить такой код max(x, func(y)) . Этот код после окончательной замены будет выглядеть вот так: ((x)>(func(y))?(x):(func(y)) . Как видим func(y) вызывается два раза. В худшем случае это может существенно повлиять на время работы.
Перевод строки в макросе. Если нужно записать макрос в нескольких строках можно использовать перевод строки \ . Думаю к этому можно обойтись и без примера.
Борьба с нежелательной точкой с запятой.
Пример 11.
Допустим у нас был такой код:

Этот код узнает сколько цифр в числе x и записывает результат в y (кстати в этом примере скобки не нужны, так как отправь мы в этот макрос не переменную, код не будет компилироваться). Тогда, если вставить его перед else , компилятор заругается на синтаксическую ошибку. Эта проблема решается вот так:

И теперь после него можно всегда ставить точку с запятой.
И еще несколько примеров.
Пример 12.
#define forn(i,a,b) for(int i = (a); i
Самый частый макрос, для упрощения написания циклов.
Пример 13.
#define sum(a,b) ((a)+(b))
Бесполезный, но прикольный макрос, для нахождения оператора + двух переменных. Сумма для чисел, конкатенация для строк и т.д.
Надеюсь эта запись будет полезна для вас.
UPD. Предупреждение 3.
Спасибо hellman1908 за пример с инкрементом.
Даже скобки не всегда могут от всего спасти. Поэтому можно либо следить за всеми переменными, или более реальный вариант — использовать такой или похожий макрос:
#define max(a,b) ((___x = (a)) > (___y = (b)) ? x : y)
Но тогда переменные ___x и ___y придется описать сразу. И тогда вылезают проблемы, ибо ___x и ___y у нас статистического типа и т.д. Поэтому полноценного варианта с помощью макроса я не знаю. Но в большинстве случаев первый работает очень хорошо. Могу посоветовать просто не кидать туда никакие выражения.

Запуск макроса из C#

Всем доброго времени суток. На связи Алексей Гулынин. В данной статье я бы хотел рассказать о том, как запустить макрос Excel в C#. Это может пригодиться при создании своего сервера отчетов, либо просто при генерации какого-либо файла Excel из приложения.

Макрос будет делать следующее: выводить таблицу умножения. Код макроса:

Код на C# будет запускать макрос. Не забываем добавить ссылку в проект на Microsoft Excel Object Library .

Как мы видим из кода — всё достаточно просто. Завершение процессов Excel я добавил для примера.

В данной статье вы научились запускать макрос в Excel, используя язык C#.

На связи был Алексей Гулынин, оставляйте свои комментарии, увидимся в следующих статьях.

Зачем использовать макросы в C?

Каждые несколько месяцев я испытываю жажду изучать немного языка Си, который никогда не покрывал мое дерьмовое образование в колледже. Сегодня это макросы. Мое базовое понимание макросов — это простой поиск и замена, которые происходят в вашем коде до его компиляции. У меня проблемы с пониманием , почему вы будете использовать макросы. Большинство основных примеров, на которые я смотрю, являются чем-то вроде

С моей точки зрения новичка кажется, что определение новой функции даст вам те же результаты. Я могу видеть, как исторически макросы были бы полезны для быстрой модификации большого количества источников за несколько дней до простого поиска и замены, но что-то подсказывает мне, что я упускаю более важный момент.

Так какие полезные вещи могут сделать макросы для вас?

Одна из причин — до C99, ключевое слово inline не было стандартным для языка C. Таким образом, макросы позволяли вам создавать небольшие функции. Они также в некотором смысле работают как шаблоны, т.е. вам не нужно указывать типы в определении макроса, например:

Этот макрос удовлетворяет целым числам, удвоениям, поплавкам и т.д.

Это не точно поиск и замена, это расширение токена. C-макросы — это то, что каждый другой вид макроса находится в вычислительном мире: способ написать что-то короткое и простое и автоматически превратиться во что-то более продолжительное и сложное.

Одной из причин использования макросов является производительность. Это способ устранения служебных издержек вызова функций, поскольку они всегда расширяются в строке, в отличие от ключевого слова inline, которое часто игнорируется подсказкой для компилятора и даже не существует (в стандарте) до C99. Например, см. Семейство макросов FD_, используемых в сочетании с fd_sets, используемыми select и pselect. Эти fd_sets на самом деле являются просто битами, а макросы FD_ скрывают операции свертывания бит. Было бы очень неприятно выписывать бит, который каждый раз наводил на себя, и вызов функции был бы большим количеством накладных расходов для такой быстрой операции, если бы она не была встроена.

Кроме того, макросы могут выполнять некоторые функции, которые не могут выполнять функции. Рассмотрим вставку маркера. Поскольку препроцессор работает перед компилятором, он может создавать новые идентификаторы для компилятора. Это может дать вам сокращенный способ создания множества похожих определений, например.

Другое дело, что функция не может превратить информацию о времени компиляции в информацию о времени выполнения:

Невозможно, чтобы функция могла использовать имя своего параметра в своем выходе, как макрос. Это также демонстрирует компиляцию кода отладки для релизов.

Реализуйте макрос

Реализуйте макрос MAX от трёх параметров, который присваивает целочисленной (int) переменной, переданной в качестве третьего аргумента, наибольшее из значений, переданных в первых двух аргументах. Пример вызова макроса:

При написании макроса постарайтесь учесть как можно больше вариантов его использования.

Требования к реализации: вам нужно реализовать только макрос MAX, функцию main и какие-либо другие функции реализовывать не нужно. Попробуйте решить эту задачу исключительно средствами макросов.

28.09.2015, 16:52

Если макрос на VBA переписать на С++, то макрос станет работать быстрее?
Всем привет!) У меня есть макрос написанный в Екселе на VBA, если его переписать на С++ , то.

Реализуйте код
помогите плиз реализовать задачу https://www.e-olymp.com/ru/contests/7631/problems/63155 вот.

Реализуйте ScopedPtr
Реализуйте ScopedPtr, который будет работать с указателями на базовый класс Expression. В этом.

Реализуйте класс SharedPtr
Уделите особое внимание «граничным случаям» — нулевой указатель, присваивание самому себе, вызов.

Реализуйте двунаправленный список
Реализуйте двунаправленный список. Подскажите в чём ошибка #include #include .

28.09.2015, 16:54 2 28.09.2015, 17:03

ForEveR, а как же помучить?))
Так круче:

А вообще не расскажите секрет использования while? Всмысле для чего? Может тоже возьму на вооружение

Меню пользователя @ UltraPenguin
28.09.2015, 17:05 4
28.09.2015, 17:05
28.09.2015, 17:09

ForEveR, блин я уж понадеялся на откровение)))

Меню пользователя @ UltraPenguin
28.09.2015, 17:25 [ТС] 6
28.09.2015, 17:27 7

Друг это шутка, может и неуместная местом)))

28.09.2015, 17:28 8
28.09.2015, 17:29 [ТС] 9
28.09.2015, 17:29 10
28.09.2015, 17:35 [ТС] 11

Увы такое я уже писал сам((
Вот что система проверки решений об этом думает:
main.cpp: In function ‘int main()’:
main.cpp:12:2: error: ‘c’ was not declared in this scope
main.cpp:12:15: error: ‘b’ cannot be used as a function
main.cpp:22:15: error: ‘b’ cannot be used as a function
main.cpp:33:15: error: ‘b’ cannot be used as a function
main.cpp:40:15: error: ‘b’ cannot be used as a function
main.cpp:47:21: error: ‘b’ cannot be used as a function
main.cpp:54:37: error: ‘b’ cannot be used as a function

* я над этой задачей уже 4 дня сижу. Просто так сюда не написал бы

Добавлено через 1 минуту
Предполагается что мы введем в макрос локальные переменные и это решит все проблемы. Честное слово, я не понимаю как такое возможно. Знающие люди подскажите код.

Почему не макросы в C #?

При обучении C # в первый раз, я был поражен тем, что у них не было никакой поддержки макросов в той же емкости , что существует в C / C ++. Я понимаю , что #define ключевое слово существует в C #, но это сильно не хватает по сравнению с тем, что я полюбила в C / C ++. Кто — нибудь знает , почему реальные макросы отсутствует C #?

Прошу прощения, если этот вопрос уже задавал в той или иной форме — я обещаю, что я потратил твердые 5 минут ищу дубли, прежде чем отправлять.

Почему не C # поддержка макросов #define? В C ++, я могу определить макрос, такие как:

#define PRODUCT(x, y, z) x * y * z

а затем использовать его в коде:

int a = PRODUCT(3, 2, 1);

C # не позволяет сделать это. Зачем?

Есть несколько причин, почему. Первый является одним из читаемости.

Одна из наших основных целей разработки для C #, чтобы сохранить код очень читаемым. Имея возможность писать макросы дает программисту возможность создать свой собственный язык — тот, который не обязательно имеет никакого отношения к тому, что код внизу. Чтобы понять, что делает этот код, пользователь должен не только понять, как работает язык, но он должен также понимать все #define макросов, которые действуют в данный момент времени. Это делает код гораздо труднее читать.

В C #, вы можете использовать методы вместо макросов, и в большинстве случаев, JIT будет встраивать их, давая вам один и тот же аспект производительности.

Там также несколько более тонкий вопрос. Макросы выполняются текстуально, а это значит, если я пишу:

int y = PRODUCT (1 + 2, 3 + 4, 5 + 6)

Я бы ожидать , чтобы получить то , что дает мне 3 * 7 *11 = 231 , но на самом деле, расширение , как я определил это дает:

int y = 1 + 2 * 3 + 4 * 5 + 6;

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

Хотя C # не строго говоря, не имеет предварительный процессор, у него есть условные символы компиляции, которые могут быть использованы для влияют на компиляцию. Они могут быть определены в коде или с параметрами для компилятора. Директивы «предварительной обработки» в C # (названный исключительно для совместимости с C / C ++, несмотря на что нет никакой отдельной стадии предварительной обработки) являются (текст, взятый из спецификации ECMA):

#define and #undef Используется для определения и отмены определения символов условной компиляции

#if, #elif, #else and #endif

Используется для условно пропустить участки исходного кода

#line Используется для контроля номеров строк, испускаемые на наличие ошибок и предупреждений.

#error and #warning Используется для выдачи ошибок и предупреждений.

#region and #endregion

Используется явным образом пометить разделы исходного кода.

Для получения дополнительной информации по выше смотрите раздел 9.5 спецификации ECMA. Условная компиляция также может быть достигнуто с помощью условного атрибута на метод, так что вызовы метода будет составлен только тогда, когда соответствующий символ определен. Для получения дополнительной информации об этом смотрите раздел 24.4.2 о Specifcation ECMA.

Высокоуровневые макросы в C#, есть ли готовые решения?

Ближе к сути. Существуют задачи, которые не можно (невозможно) универсализировать через C#, нужно тупо копипастить код с некоторыми изменениями. Да можно пытаться, но кода меньше не станет. Это бесполезно. Код упирается в элементарные лексемы языка, универсализация дальше — кодогенерация.
И вот пример, который аж в стул поджаривает — реализация INotifyPropertyChanged.
Ссылка на существующее решение проблемы.
Все что предложили: T4 или ручками с использованием базового класса. Но блин, почему я должен смотреть на этот код, который пусть даже сгенерирован автоматически, если он меня бесит (не надо просить выкалывать глаза или не смотреть на него).
Его самая примитивная реализация для одного свойства включает как минимум 5 строк (примитивная в смысле ф-ционала, то есть просто уведомить о изменение значения):
Название метода
Красивая открывающая фигурная скобочка
Геттер
Сеттер
Красивая закрывающая фигурная
Вот такая вот беда. Вижу такой код и хочу убивать. Если написать кодогенератор, то можно реализовать это так:
Есть проект 1 — с которым ты активно работаешь, там ты используешь всякие декларативные штучки и наслаждаешься жизнью. Со вторым работает кодогенератор, и ты, когда производишь запуск.
Решение проблемы выше могло бы лежать в объявлении атрибута над автоматическим свойством — все.

Чего хочется?
Платформа для аддонов-плюшек декларативного кодинга (не спутайте (да мало ли с чем у вас это ассоциируется), я про сложную умную кодогенерацию).
То есть написать какую-нибудь либу, которая просто обеспечивает данные на вход и на выход. То есть, чтобы написать какой-то декларативный аддон (как для штуки выше, к примеру), мне надо просто получить экземпляр Assembly входной сборки, и методы, вроде апи, чтобы вносить модификации в исходники и структуру output-проекта.

Естественно, я хочу поинтересоваться, не хочу ли я велосипеда. Если нет, то, может, кто-то желал бы помочь в столь благом деле советом, а, может, и делом.

Добавлено через 2 минуты
Также если у кого есть желание, думаю было бы очень удобно сделать интеграцию с VS в виде расширения, но такого опыта у меня нет.

Илон Маск рекомендует:  Добавление элемента
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL