Я всегда видел примеры и случаи, когда использование макроса лучше, чем использование функции.
Может ли кто-нибудь объяснить мне пример с недостатком макроса по сравнению с функцией?
Я всегда видел примеры и случаи, когда использование макроса лучше, чем использование функции.
Может ли кто-нибудь объяснить мне пример с недостатком макроса по сравнению с функцией?
Макросы подвержены ошибкам, поскольку полагаются на текстовое замещение и не выполняют проверку типов. Например, этот макрос:
#define square(a) a * a
отлично работает при использовании целого числа:
square(5) --> 5 * 5 --> 25
но делает очень странные вещи при использовании с выражениями:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
Помещение круглых скобок в аргументы помогает, но не полностью устраняет эти проблемы.
Когда макросы содержат несколько операторов, у вас могут возникнуть проблемы с конструкциями control-flow:
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
Обычная стратегия для исправления этого - помещать операторы в цикл "do {...} while (0)".
Если у вас есть две структуры, которые содержат поле с одним и тем же именем, но разной семантикой, один и тот же макрос может работать с обоими, что приведет к странным результатам:
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
Наконец, макросы могут быть сложными для отладки, приводя к странным синтаксическим ошибкам или ошибкам времени выполнения, которые вам необходимо раскрыть для понимания (например, с помощью gcc -E), поскольку отладчики не могут проходить через макросы, как в этом примере:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
Встроенные функции и константы помогают избежать многих из этих проблем с макросами, но не всегда применимы. В тех случаях, когда макросы преднамеренно используются для определения полиморфного поведения, непреднамеренного полиморфизма может быть трудно избежать. C++ имеет ряд функций, таких как шаблоны, которые помогают создавать сложные полиморфные конструкции безопасным для типов способом без использования макросов; см. Stroustrup Язык программирования C++ для подробностей.
Свойства макроса:
Функциональные функции:
Побочные эффекты являются серьезными. Вот типичный случай:
#define min(a, b) (a < b ? a : b)
min(x++, y)
расширяется до:
(x++ < y ? x++ : y)
x
увеличивается в два раза в одном выражении. (и неопределенное поведение)
Написание многострочных макросов также является проблемой:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Им требуется \
в конце каждой строки.
Макросы не могут ничего "вернуть", если вы не сделаете это одним выражением:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
Невозможно сделать это в макросе, если вы не используете оператор выражения GCC. (ОБНОВЛЕНИЕ: Вы можете использовать оператор запятой, хотя... упустил из виду... Но он все еще может быть менее читабельным.)
Порядок действий: (любезно предоставлено @ouah)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
расширяется до:
(x & 0xFF < 42 ? x & 0xFF : 42)
Но &
имеет более низкий приоритет, чем <
. Таким образом, 0xFF < 42
оценивается первым.
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
тогда:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
По сравнению с:
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
Повторная проверка параметров и кода не повторяется, что может привести к раздуванию кода. Синтаксис макросов также может приводить к любому числу странных случаев кросс, где полуколония или порядок приоритета могут мешать. Здесь ссылка, которая демонстрирует макрос зло
В случае сомнений используйте функции (или встроенные функции).
Однако ответы здесь в основном объясняют проблемы с макросами, а не простое представление о том, что макросы являются злыми, потому что возможны глупые несчастные случаи.
Вы можете знать о подводных камнях и учиться избегать их. Затем используйте макросы только тогда, когда есть веская причина.
Существуют определенные исключительные случаи, когда есть преимущества использования макросов, в том числе:
va_args
. Например, fooobar.com/questions/79451/....__FILE__
, __LINE__
, __func__
). проверьте условия pre/post, assert
при сбое или даже статические утверждения, поэтому код не будет компилироваться при неправильном использовании (в основном полезно для отладочных сборников).struct
. Элементы присутствуют перед литьем (может быть полезна для полиморфных типов). func(FOO, "FOO");
, вы можете определить макрос, который расширяет для вас строку func_wrapper(FOO);
inline
могут быть опцией).По общему признанию, некоторые из них полагаются на расширения компилятора, которые не являются стандартными C. Смысл в том, что у вас может быть меньше портативного кода или у него должно быть ifdef
, поэтому они используются только в том случае, когда компилятор поддерживает.
Отмечая это, поскольку это одна из наиболее распространенных причин ошибок в макросах (например, передача в x++
, где макрос может увеличиваться несколько раз).
его можно написать макросы, которые избегают побочных эффектов с множественным экземпляром аргументов.
Если вам нравится иметь макрос square
, который работает с различными типами и поддерживает C11, вы можете сделать это...
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
Это расширение компилятора, поддерживаемое GCC, Clang, EKOPath и Intel С++ (но не MSVC);
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
Таким образом, недостатком макросов является то, что вам нужно знать, чтобы использовать их для начала, и что они не поддерживаются так широко.
Одно из преимуществ заключается в том, что в этом случае вы можете использовать ту же функцию square
для разных типов.
один недостаток макросов заключается в том, что отладчики читают исходный код, который не имеет расширенных макросов, поэтому запуск отладчика в макросе необязательно полезен. Излишне говорить, что вы не можете установить точку останова внутри макроса, как вы можете, с помощью функций.
Добавление к этому ответу.
Макросы подставляются непосредственно в программу препроцессором (так как в основном это директивы препроцессора). Поэтому они неизбежно используют больше памяти, чем соответствующую функцию. С другой стороны, функция требует больше времени для вызова и возврата результатов, и эти служебные данные можно избежать с помощью макросов.
Также макросы имеют некоторые специальные инструменты, чем могут помочь с переносимостью программы на разных платформах.
Макросам не нужно назначать тип данных для своих аргументов в отличие от функций.
В целом они полезны в программировании. И как макроинструкции, так и функции могут использоваться в зависимости от обстоятельств.
Функции выполняют проверку типов. Это дает вам дополнительный уровень безопасности.
Я не заметил в ответах выше одно преимущество функций над макросами, которые, как мне кажется, очень важны:
Функции могут передаваться как аргументы, макросы не могут.
Конкретный пример: вы хотите написать альтернативную версию стандартной функции strpbrk, которая будет принимать, а не явный список символов для поиска внутри другой строки, функцию (указатель на a), которая будет возвращать 0 до тех пор, пока символ не будет что проходит некоторый тест (определяемый пользователем). Одна из причин, по которой вы, возможно, захотите сделать это, - это использовать другие стандартные функции библиотеки: вместо предоставления явной строки, полной пунктуации, вы можете передать ctype.h "ispunct" вместо этого и т.д. Если "ispunct" был реализован только как макрос, это не сработает.
Есть много других примеров. Например, если ваше сравнение выполняется макросом, а не функцией, вы не можете передать его в stdlib.h 'qsort'.
Аналогичной ситуацией в Python является "печать" в версии 2 по сравнению с версией 3 (непропускаемая инструкция против пропускаемой функции).
Если вы передадите функцию в качестве аргумента макросу, она будет оцениваться каждый раз. Например, если вы вызываете один из самых популярных макросов:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
вот так
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
functionThatTakeLongTime будет оцениваться 5 раз, что может значительно снизить производительность