Когда следует использовать макросы вместо встроенных функций? - PullRequest
12 голосов
/ 29 октября 2009

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

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

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

Ответы [ 8 ]

8 голосов
/ 29 октября 2009

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

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

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

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

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

7 голосов
/ 29 октября 2009

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

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;

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

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

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

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

6 голосов
/ 29 октября 2009

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

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

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

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

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

#define radian2degree(a) (a * 57.295779513082)

должно быть

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

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

2 голосов
/ 29 октября 2009

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

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

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

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

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

1 голос
/ 29 октября 2009

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

1 голос
/ 29 октября 2009

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

определяет может быть переопределено неопределено, и нет проверки типа.

0 голосов
/ 29 октября 2009

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

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

0 голосов
/ 29 октября 2009

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

...