Когда следует использовать макросы вместо встроенных функций?

StackOverflow https://stackoverflow.com/questions/1640344

  •  08-07-2019
  •  | 
  •  

Вопрос

В предыдущем вопрос то, что я считал хорошим ответом, было отклонено за предложенное использование макросов

#define radian2degree(a) (a * 57.295779513082)
#define degree2radian(a) (a * 0.017453292519)

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

Это было полезно?

Решение

В макросах есть пара сугубо злых вещей.

Они обрабатывают текст и не имеют границ. Если вы #define foo 1 , любое последующее использование foo в качестве идентификатора завершится неудачей. Это может привести к странным ошибкам компиляции и трудно обнаруживаемым ошибкам во время выполнения.

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

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

В вашем случае вам нужны круглые скобки:

#define radian2degree(a) (a * 57.295779513082)

должен быть

#define radian2degree(a) ((a) * 57.295779513082)

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

Другие советы

В большинстве других ответов обсуждается, почему макросы являются злом, включая то, что в вашем примере есть общий недостаток использования макросов.Вот мнение Страуструпа: http://www.research.att.com /~bs/bs_faq2.html#макрос

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

  • вставка токена
  • работа с номерами строк или подобными (например, для создания сообщений об ошибках в assert())
  • работа с вещами, которые не являются выражениями (например, сколько реализаций offsetof() используйте использование имени типа для создания операции приведения)
  • макрос для получения количества элементов массива (не могу сделать это с помощью функции, так как имя массива слишком легко преобразуется в указатель)
  • создание функций типа "полиморфный" -подобных вещей в C, где шаблоны недоступны

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

Я также перешел к использованию перечислений для именованных числовых констант вместо #define.

Для этого конкретного макроса, если я использую его следующим образом:

int x=1;
x = radian2degree(x);
float y=1;
y = radian2degree(y);

проверки типа не будет, и x, y будут содержать разные значения.

Кроме того, следующий код

float x=1, y=2;
float z = radian2degree(x+y);

не будет делать то, что вы думаете, так как это переведет на

float z = x+y*0.017453292519;

вместо того, чтобы

float z = (x+y)+0.017453292519;

что и является ожидаемым результатом.

Это всего лишь несколько примеров неправильного поведения и неправильного использования макросов.

Редактировать

вы можете ознакомиться с дополнительными обсуждениями по этому поводу здесь

Макросами относительно часто злоупотребляют, и при их использовании можно легко допустить ошибки, как показано на вашем примере.Возьмем выражение radian2degree(1 + 1):

  • с помощью макроса он расширится до 1 + 1 * 57.29...= 58.29...
  • с функцией это будет то, чем вы хотите, чтобы это было, а именно (1 + 1) * 57.29...= ...

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

#define radian2degree(a) ((a) * 57.295779513082)

Но вы должны придерживаться встроенных функций.Смотрите эти ссылки из C ++ FAQ Lite для получения дополнительных примеров макросов evil и их тонкостей:

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

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

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

Макросы - это зло, потому что вы можете передать в них больше, чем переменную или скаляр, и это может привести к нежелательному поведению (определите макрос max, чтобы определить max между a и b, но передайте a ++ и b ++ макросу и посмотрим что получится).

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top