Какая разница на практике между inline и #define?

Как говорится в названии; какая разница на практике между ключевым словом inline и директивой препроцессора #define?

Ответ 1

#define является инструментом препроцессора и имеет макро семантику. Рассмотрим это, если max(a,b) - макрос, определенный как

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

Пример 1:

val = max(100, GetBloodSample(BS_LDL)) будет проливать лишнюю невинную кровь, потому что функция фактически будет вызвана дважды. Это может означать значительную разницу в производительности для реальных приложений.

Пример 2:

val = max(3, schroedingerCat.GetNumPaws()) Это демонстрирует серьезную разницу в программной логике, потому что это может неожиданно вернуть число, которое меньше 3 - то, чего пользователь не ожидал бы.

Пример 3:

val = max(x, y++) может увеличивать y более одного раза.


С встроенными функциями ничего из этого не произойдет.

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

Ответ 2

Функции (независимо от того, inline или нет) и макросы выполняют разные цели. Их различие не следует рассматривать как идеологическое, как некоторые, похоже, воспринимают его, и что еще более важно, они могут хорошо работать вместе.

Макросы - это замена текста, которая выполняется во время компиляции, и они могут делать такие вещи, как

#define P99_ISSIGNED(T) ((T)-1 < (T)0)

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

Настроены функции (inline), что делает их более строгими или, выражаясь отрицательно, менее гибкими. Рассмотрим функции

inline uintmax_t absU(uintmax_t a) { return a; }
inline uintmax_t absS(uintmax_t a) {
   return (-a < a) ? -a : a;
}

Первый реализует тривиальную функцию abs для неподписанного интегрального типа. Второй реализует его для подписанного типа. (да, в аргументе используется unsigned, это для цели.)

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

Теперь со следующим макросом

#define ABS(T, A) ((T)(P99_ISSIGNED(T) ? absS : absU)(A))

мы внедрили

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

(Ну, я признаю, что делать это с помощью abs немного искусственно, но я надеюсь, что вы получите картину.)

Ответ 3

Макросы (созданные с помощью #define) всегда заменяются как написанные и могут иметь проблемы с двойной оценкой.

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

Ответ 4

Ну, многострочный #define сложнее писать и редактировать, чем встроенную функцию. Вы можете определить встроенную функцию, как и любую нормальную функцию, и можете определять переменные без проблем. Представьте, что вы хотите вызвать блок кода несколько раз в рамках другой функции, и для этого блока кода нужны свои собственные переменные: это проще сделать с встроенными функциями (да, вы можете сделать это с помощью #defines и do { ... } while (0);, но это то, что вы должны думать).

Кроме того, с включенной отладкой вы обычно получаете "смоделированный" стек-фрейм для встроенных функций, которые иногда могут облегчать отладку (по крайней мере, то, что вы получаете при компиляции/ссылке с gcc -g и отладке с помощью GDB, IIRC). Вы можете разместить точки останова внутри своей встроенной функции.

Кроме того, результат должен быть почти идентичным, AFAIK.

Ответ 6

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