Встроенные функции против макросов препроцессора

Ответы:

127

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

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

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

  • Макросы небезопасны по типу и могут быть расширены независимо от того, являются ли они синтаксически правильными - на этапе компиляции будут сообщаться об ошибках, вызванных проблемами расширения макроса.
  • Макросы можно использовать там, где вы не ожидаете, что приведет к проблемам
  • Макросы более гибкие, поскольку они могут расширять другие макросы, в то время как встроенные функции не обязательно это делают.
  • Макросы могут вызывать побочные эффекты из-за их расширения, поскольку входные выражения копируются везде, где они появляются в шаблоне.
  • Встроенная функция не всегда гарантированно встроена - некоторые компиляторы делают это только в сборках выпуска или когда они специально настроены для этого. Кроме того, в некоторых случаях встраивание может быть невозможно.
  • Встроенные функции могут предоставлять область видимости для переменных (особенно статических), макросы препроцессора могут делать это только в блоках кода {...}, а статические переменные не будут вести себя точно так же.
LBushkin
источник
39
Не всегда гарантируется, что встроенная функция будет встроенной: потому что компилятор не будет встроенным, если это будет генерировать более медленный код и т. Д. Компилятор выполняет большой анализ, который не может выполнить инженер, и делает правильные вещи.
Мартин Йорк
14
Я считаю, что рекурсивные функции - еще один пример, когда большинство компиляторов игнорируют встраивание.
LBushkin
Есть ли в этом случае какие-либо важные отличия в C от C ++?
rzetterberg
7
Один момент, о котором не упоминается, заключается в том, что на встраивание могут влиять флаги компиляции. Например, когда вы строите для максимальной скорости (например, GCC -O2 / -O3), компилятор выберет встраивание многих функций, но когда вы строите для минимального размера (-Os), если обычно встроенные функции вызываются только один раз (или очень маленькие функции ). С макросами такого выбора нет.
dbrank0
Макросы не могут охватывать спецификатор доступа (например, частный или защищенный), пока возможны встроенные функции.
Hit's
78

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

Например, если вы хотите сравнить 2 значения:

#define max(a,b) ((a<b)?b:a)

Побочные эффекты появляются, если вы используете, max(a++,b++)например ( aили bбудут увеличиваться вдвое). Вместо этого используйте (например)

inline int max( int a, int b) { return ((a<b)?b:a); }
ThibThib
источник
3
Просто хочу добавить к вашему примеру, что помимо побочного эффекта, макрос также может вводить дополнительную рабочую нагрузку, max(fibonacci(100), factorial(10000))
учтите
Все говорят о проверке типов, но только вы привели реальный пример, поэтому я поддерживаю этот ответ.
Иванзиньо
16

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

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

  • Во время расширения макроса могут возникнуть нежелательные результаты и неэффективность из-за переоценки аргументов и порядка операций. Например

    #define MAX(a,b) ((a)>(b) ? (a) : (b))
    int i = 5, j = MAX(i++, 0);
    

    приведет к

    int i = 5, j = ((i++)>(0) ? (i++) : (0));
  • Аргументы макроса не оцениваются перед раскрытием макроса

    #define MUL(a, b) a*b
    int main()
    {
      // The macro is expended as 2 + 3 * 3 + 5, not as 5*8
      printf("%d", MUL(2+3, 3+5));
     return 0;
    }
    // Output: 16`
    
  • Ключевое слово return нельзя использовать в макросах для возврата значений, как в случае функций.

  • Встроенные функции могут быть перегружены

  • Токены, передаваемые в макросы, могут быть объединены с помощью оператора ##, называемого оператором вставки токена.

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

DataCruncher
источник
13

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

Вот несколько других, менее очевидных моментов.

Sharptooth
источник
11

Чтобы добавить еще одно отличие к уже приведенным: вы не можете пройти через #defineотладчик, но можете пройти через встроенную функцию.

RichieHindle
источник
8

Макросы игнорируют пространства имен. И это делает их злыми.

Кирилл Васильевич Лядвинский
источник
3

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

  • Встроенные функции следуют всем протоколам безопасности типов, применяемым к обычным функциям.
  • Встроенные функции указываются с использованием того же синтаксиса, что и любые другие функции, за исключением того, что они включают ключевое слово inline в объявление функции.
  • Выражения, переданные в качестве аргументов встроенным функциям, оцениваются один раз.
  • В некоторых случаях выражения, переданные в качестве аргументов макросу, можно вычислить более одного раза. http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx

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

- хорошая статья : http://www.codeguru.com/forum/showpost.php?p=1093923&postcount=1

;

Ahmad
источник
2

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

Майкл Донохью
источник
1

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

heeen
источник
1

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

ФУНКЦИИ :

int Square(int x){
return(x*X);
}
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • Вызовы функций связаны с накладными расходами, поскольку после завершения выполнения функции она должна знать, куда она должна вернуться, а также должна сохранить значение в памяти стека.

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

MACROS:

# define Square(x) x*x;
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • Макросы работают на этапе предварительной обработки, т.е. на этом этапе операторы, написанные с ключевым словом #, будут заменены содержимым, т.е.

int результат = Квадрат (x * x)

Но у макросов есть ошибки, связанные с этим.

#define Square(x) x*x
int main() {
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

Здесь результат 11, а не 36 .

ВСТРОЕННЫЕ ФУНКЦИИ :

inline int Square(int x) {
    return x * x;
}

int main() {
    using namespace std;
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

Результат 36

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

Сравнение макросов и встроенных функций:

  1. Макросы работают через подстановку, тогда как во встроенных функциях вызов функции заменяется телом.
  2. Макросы подвержены ошибкам из-за подстановки, в то время как встроенные функции безопасны в использовании.
  3. У макросов нет адреса, тогда как у встроенных функций есть адрес.
  4. Макросы сложно использовать с несколькими строками кода, а встроенные функции - нет.
  5. В C ++ макросы нельзя использовать с функциями-членами, тогда как встроенные функции могут быть.

ВЫВОД:

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

  • большие функции
  • функции, имеющие слишком много условных аргументов
  • рекурсивный код и код с циклами и т. д.

что хорошо, потому что это всякий раз, когда компилятор считает, что лучше всего поступить по-другому.

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

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

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

#define MACRO_FUNC(X) ...

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

if(MACRO_FUNC(y)) {
 ...body
}

Там можно было без проблем использовать обычную функцию.

Falaina
источник
0

С точки зрения кодирования встроенная функция похожа на функцию. Таким образом, различия между встроенной функцией и макросом такие же, как и различия между функцией и макросом.

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

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

Брайан
источник
0

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

Арашдип Сингх
источник
-1
#include<iostream>
using namespace std;
#define NUMBER 10 //macros are preprocessed while functions are not.
int number()
{ 
    return 10;
}
/*In macros, no type checking(incompatible operand, etc.) is done and thus use of micros can lead to errors/side-effects in some cases. 
However, this is not the case with functions.
Also, macros do not check for compilation error (if any). Consider:- */
#define CUBE(b) b*b*b
int cube(int a)
{
 return a*a*a;
}
int main()
{
 cout<<NUMBER<<endl<<number()<<endl;
 cout<<CUBE(1+3); //Unexpected output 10
 cout<<endl<<cube(1+3);// As expected 64
 return 0;
}

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

Некоторые недостатки макросов: отсутствует проверка типов; трудно отлаживать, поскольку они вызывают простую замену; макрос не имеет пространства имен, поэтому макрос в одном разделе кода может повлиять на другой раздел. Макросы могут вызывать побочные эффекты, как показано в примере CUBE () выше.

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

rahul_107
источник
Насколько больше удовольствия вы получите от #define TWO_N(n) 2 << nэтого cout << CUBE(TWO_N(3 + 1)) << endl;? (Лучше заканчивать строки вывода, endlчем начинать их с него.)
Джонатан Леффлер,