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

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

10
задан Brian Tompsett - 汤莱恩 27 November 2015 в 17:36
поделиться

6 ответов

#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 более одного раза.


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

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

42
ответ дан 3 December 2019 в 13:32
поделиться

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

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

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

1
ответ дан 3 December 2019 в 13:32
поделиться

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

0
ответ дан 3 December 2019 в 13:32
поделиться

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

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

1
ответ дан 3 December 2019 в 13:32
поделиться

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

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

#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 для целочисленного типа без знака. Второй реализует его для подписанного типа. (да, он принимает беззнаковый аргумент в качестве аргумента, это специально.)

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

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

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

мы реализовали

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

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

2
ответ дан 3 December 2019 в 13:32
поделиться

Разница подробно описана здесь: http://www.geekinterview.com/question_details/23831 ура

0
ответ дан 3 December 2019 в 13:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: