"#Define" и inline ведут себя одинаково? - PullRequest
3 голосов
/ 05 июля 2011

У меня есть несколько коротких определений в одном из моих заголовков, таких как:

#define ROUND_DOWN(a,b)   (a)-(a)%(b)

например.

ROUND_DOWN(178,32) = 160

Но если я передам это:

ROUND_DOWN(160*2, 32);

тогда как это скомпилируется?

(160*2)-(160*2)%(32), 

, что просто больше обработки, чем 160 * 2 в два раза ..

Мне интересно, работают ли встроенные функции таким же образом? например,

inline int RoundDown(int a, int b)
{
return (a)-(a)%(b)
}

Будет ли 160 * 2 сохраняться в "int a" как 320, и тогда будет работать вычисление, или оно будет вести себя так же, как определение?

Лучшим примером является вызов:

RoundDown((x+x2)*zoom, tile_width);

Ответы [ 5 ]

8 голосов
/ 05 июля 2011

Одинаково ли ведут себя #define и inline?

Нет, они не делают!

Существует ряд различий между макросом и встроенной функцией.

- Нет оценок времени

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

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

A Пример кода:

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

int main()
{

  int a = 0;
  int b = 1;

  int c = max(a++, b++);

  cout << a << endl << b << endl;
  return 0;

}

Вероятно, предполагалось напечатать 1 и 2, но макрос расширился до:

int c = a++ > b++ ? a++ : b++;

b увеличивается в два раза, и программа печатает 1 и 3.

- Кто их оценивает

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

- Проверка типа

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

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

Пример кода:

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

int main( void)
{
   cout << "Maximum of 10 and 20 is " << MAX("20", "10") << endl;
   return 0;
}

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

- Предложение или команда?

Inline - это всего лишь предложение для компилятора. Компилятор решает, расширять функцию или нет.

Макросы всегда будут расширяться.

- Как насчет отладки?

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

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

5 голосов
/ 05 июля 2011

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

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

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

3 голосов
/ 05 июля 2011

#define s - это простые текстовые замены, поэтому (как вы заметили) вам может потребоваться быть осторожным с круглыми скобками и т. Д. inline параметры анализируются нормально.

Существует проблема , связанная с условиями.

2 голосов
/ 05 июля 2011

Номинально, аргумент функции 160*2 вычисляется ровно один раз, а затем результат используется в теле функции, тогда как макрос оценивает 160*2 дважды. Если выражение аргумента имеет побочные эффекты, вы можете увидеть это [*]: ROUND_DOWN(printf("hi!\n"), 1); против RoundDown(printf("hi!\n"), 1);

На практике, независимо от того, является ли функция встроенной или макрокомандой, это просто целочисленная арифметика в выражении без побочных эффектов. Оптимизирующий компилятор может обработать результат всего вызова макроса / функции и просто вставить ответ в выдаваемый код. Таким образом, вы можете обнаружить, что ваш макрос и ваша встроенная функция приводят к тому, что исполняется один и тот же код, и поэтому int a = ROUND_DOWN(160*2, 32); и int a = RoundDown(160*2, 32); могут совпадать с int a = 320;.

Там, где нет побочных эффектов, оптимизация также может сохранять и повторно использовать промежуточные результаты. Так что int c = ROUND_DONW(a*2, b); может в итоге выдать код, который выглядит так, как будто вы написали:

int tmp = a*2;
int c = tmp - tmp % b;

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

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

int a = ROUND_DOWN(321, 32) * 2;

, а затем потратить несколько минут на размышления, почему результат равен 319.

[*] Хотя не увлекайтесь - для некоторых выражений с побочными эффектами, например i++, где i - целое число, макрос имеет неопределенное поведение из-за отсутствия точек последовательности.

1 голос
/ 05 июля 2011

Хорошо, в примере, который вы привели с константами, на любом приемлемом компиляторе обе версии будут вычислять константу во время компиляции.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...