Что такое макрос? Разница между макросом и функцией?

16

Я не очень хорошо понимаю концепцию макроса. Что такое макрос? Я не понимаю, как это отличается от функции? И функция, и макрос содержат блок кода. Так чем же отличаются макрос и функция?

Эдоардо Соргентоне
источник
15
Вы имеете в виду какой-то конкретный язык программирования?
mkrieger1
15
Вы должны быть более конкретными; макрос относится к трем очень различным концепциям, примерно макросы текста / препроцессора в стиле C, макросы Lisp и макросы приложений.
Хрилис - на забастовке -

Ответы:

7

Заметка

Я хотел бы добавить следующее разъяснение после наблюдения поляризационной схемы голосования по этому ответу.

Ответ не был написан с учетом технической точности и широкого обобщения. Это была скромная попытка объяснить простым языком разницу между макросами и функциями новичку в программировании, не пытаясь быть полным или точным (на самом деле это далеко не так). Язык программирования C был в моей голове при составлении ответа, но я извиняюсь за то, что не упомянул его четко и не вызвал (потенциальную) путаницу.

Я действительно расцениваю ответ, которым поделился Йорг Миттаг . Это было проницательное чтение (насколько я знаю), и я проголосовал за него вскоре после того, как он был опубликован. Я только начал изучать стек разработки программного обеспечения, и опыт и обсуждения до сих пор были действительно проницательными.

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


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

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

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

  • Использование постоянных значений (таких как математические или научные значения) или какого-либо специального программного параметра.

  • Печать сообщений журнала или обработка утверждений.

  • Выполнение простых расчетов или проверок условий.

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

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

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

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

Нимеш Нима
источник
9
А как насчет функции встраивания?
Натан Купер
1
В языке C & co макрос также может быть вызовом функции. Так что проводить различие между ними не имеет особого смысла.
Сомбреро Курица
5
А макросы в стиле C не являются чем-то отдельным, они не вводят лексическую область видимости и имеют неограниченный доступ к локальным именам в области видимости в точке замещения.
бесполезно
@Sombrero Chicken: Может быть, вы могли бы показать пример этого? У вас могут быть макросы, которые содержат функции, но (AFAIK) макрос не является вызовом функции.
jamesqf
3
В этом ответе есть много вещей, которые вводят в заблуждение. Приведенные вами примеры абсолютно ни в коем случае не являются ситуациями, когда макросы являются «предпочтительными» - на самом деле, наоборот. Макросы не должны использоваться для этих вещей, если нет веских причин для этого. Производительность вообще не является хорошим обоснованием для использования макросов вообще. По причинам, упомянутым в других комментариях, оправдание использования макросов таким способом может привести к подверженному ошибкам коду со всевозможными непреднамеренными последствиями, особенно в большей кодовой базе.
Бен Коттрелл
65

К сожалению, в программировании существует множество различных вариантов использования термина «макрос».

В семействе языков Lisp и воодушевленных ими языках, а также во многих современных функциональных или функционально-вдохновленных языках, таких как Scala и Haskell, а также в некоторых императивных языках, таких как Boo, макрос - это фрагмент кода, который выполняется во время компиляции. (или, по крайней мере, до времени выполнения для реализаций без компилятора) и может преобразовать Абстрактное Синтаксическое Дерево (или какой-либо эквивалент в конкретном языке, например, в Лиспе, это будут S-выражения) во что-то другое во время компиляции. Например, во многих реализациях Scheme forэто макрос, который расширяется в несколько обращений к телу. В статически типизированных языках макросы часто являются типобезопасными, то есть не могут генерировать код, который не является типизированным.

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

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

В сценариях приложения «макрос» относится к серии действий, которые пользователь может «записать» и «воспроизвести».

Все это в некотором смысле виды исполняемого кода, что означает, что они могут в некотором смысле рассматриваться как функции. Однако в случае макросов Lisp, например, их ввод и вывод являются фрагментами программы. В случае C их вход и выход являются токенами. Первые три также имеют очень важное различие в том, что они выполняются во время компиляции . Фактически, макросы препроцессора C, как следует из названия, фактически выполняются еще до того, как код достигнет компилятора .

Йорг Миттаг
источник
1
«В статически типизированных языках макросы часто являются типобезопасными, то есть не могут генерировать код, который не является типизированным» - правда? На какие языки вы ссылаетесь? AFAICS, это, как правило, можно гарантировать только на языке с зависимой типизацией. В Haskell макросы TH являются только синтаксически безопасными, но проверка типов запускается впоследствии. (Таким образом, в отношении типов они дают еще меньше гарантий, чем шаблоны C ++).
оставлено около
1
Кроме сценариев приложений, я не думаю, что три примера, которые вы приводите, отличаются друг от друга. Все они выполняют какую-то подстановку в программе, а не переходят к общему фрагменту кода при его выполнении.
IMSoP
Возможно, вы можете расширить упоминание макросов C с помощью общей концепции языков макросов, таких как M4, поскольку для C это в основном то, что спецификация определяет вспомогательный язык макросов CPP и стандартизирует его использование с C.
JoL
1
Общая тема, по-видимому, заключается в том, что макросы генерируют код, который интерпретируется как часть исходного кода более крупной программы, а не исполняется при запуске программы.
jpmc26
1
@ jpmc26 Я бы сказал, что общая тема - замена, когда вы делаете маленькую вещь, и она расширяется до большой вещи. Язык макросов M4 не предназначен для кода. Вы можете использовать его для создания любого текста. Есть также клавиатурные макросы, такие как упомянутый ответ. Вы нажимаете 1 или 2 клавиши, и они расширяются до большего количества нажатий клавиш. макросы vim тоже такие. Сохраните большую последовательность команд нормального режима под одной клавишей, и вы вызываете эту последовательность, выполняя команду нормального режима, которая ее выполняет.
JoL
2

В семействе языков C определение макроса , команда препроцессора, задает параметризованный шаблон кода, который подставляется при вызове макроса без компиляции в определении. Это означает, что все свободные переменные должны быть связаны в контексте вызова макроса. Аргументы параметра с побочным эффектом i++могут повторяться путем многократного использования параметра. Текстовая подстановка аргумента 1 + 2некоторого параметра xпроисходит перед компиляцией и может вызвать непредвиденное поведение x * 3( 7io 9). Ошибка в теле макроса определения макроса будет отображаться только при компиляции при вызове макроса.

Определение функции определяет код со свободными переменными, привязанными к контексту тела функции; не вызов функции .

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

Joop Eggen
источник
2

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

Макрос, напротив, принимает некоторый неоцененный синтаксис и оперирует этим. Для языков, подобных C, синтаксис вводится на уровне токенов. Для языков с LISP-подобными макросами они получают синтаксис, представленный в виде AST. Макрос должен возвращать новый фрагмент синтаксиса.

Эштон Виерсдорф
источник
0

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

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

Как говорится в ответе Йорга В. Миттага , точные детали различаются в зависимости от языка: в некоторых, таких как C, макрос выполняет подстановку текста в исходном коде; в некоторых, таких как Lisp, он выполняет манипуляции с промежуточной формой, такой как абстрактное синтаксическое дерево. Есть также некоторые серые области: некоторые языки имеют обозначения для «встроенных функций», которые определены как функция, но расширены в скомпилированную программу как макрос.

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

IMSoP
источник
0

Макрос выполняется во время компиляции, а функция выполняется во время выполнения.

Пример:

#include <stdio.h>

#define macro_sum(x,y) (x+y)

int func_sum(x,y) {
    return x+y;
}

int main(void) {
    printf("%d\n", macro_sum(2,3));
    printf("%d\n", func_sum(2,3));
    return 0;
}

Таким образом, во время компиляции код фактически изменяется на:

#include <stdio.h>

int func_sum(x,y) {
    return x+y;
}

int main(void) {
    printf("%d\n", (2+3));
    printf("%d\n", func_sum(2,3));
    return 0;
}
david72
источник