Подтвердить что ты не робот

Макро против функции в C

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

Может ли кто-нибудь объяснить мне пример с недостатком макроса по сравнению с функцией?

4b9b3361

Ответ 1

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

#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++ для подробностей.

Ответ 2

Свойства макроса:

  • Макрос Предварительно обработанный
  • Нет проверки типов
  • Длина кода Увеличивает
  • Использование макроса может привести к побочному эффекту
  • Скорость выполнения Быстрее
  • Перед компиляцией имя макроса заменяется значением макроса
  • Полезно, когда маленький код появляется много времени
  • Макрос не Проверьте ошибки компиляции

Функциональные функции:

  • Функция Скомпилирована
  • Выполняется проверка типа
  • Длина кода остается Тот же
  • Нет побочный эффект
  • Скорость выполнения Медленнее
  • Во время вызова функции происходит передача управления
  • Полезно, когда большой код появляется много времени
  • Проверка функций Скомпилировать ошибки

Ответ 3

Побочные эффекты являются серьезными. Вот типичный случай:

#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 оценивается первым.

Ответ 4

Пример 1:

#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;
}

Пример 2:

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;
}

Ответ 5

Повторная проверка параметров и кода не повторяется, что может привести к раздуванию кода. Синтаксис макросов также может приводить к любому числу странных случаев кросс, где полуколония или порядок приоритета могут мешать. Здесь ссылка, которая демонстрирует макрос зло

Ответ 6

В случае сомнений используйте функции (или встроенные функции).

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

Существуют определенные исключительные случаи, когда есть преимущества использования макросов, в том числе:

  • Общие функции, как указано ниже, могут иметь макрос, который можно использовать для разных типов входных аргументов.
  • Переменная количество аргументов может отображаться для разных функций вместо использования C va_args. Например, fooobar.com/questions/79451/....
  • Они могут необязательно включать локальную информацию, такую ​​как строки отладки:
    (__FILE__, __LINE__, __func__). проверьте условия pre/post, assert при сбое или даже статические утверждения, поэтому код не будет компилироваться при неправильном использовании (в основном полезно для отладочных сборников).
  • Проверяйте входные аргументы, вы можете делать тесты на входных аргументах, такие как проверка их типа, sizeof, check struct. Элементы присутствуют перед литьем (может быть полезна для полиморфных типов).
    Или проверить массив соответствует некоторому условию длины.
    см. fooobar.com/questions/79452/...
  • В то время как его отметили, что функции выполняют проверку типов, C также будет принуждать значения (например, ints/floats). В редких случаях это может быть проблематично. Его можно написать макросы, которые более требовательны, чем функция их входных аргументов. см. fooobar.com/questions/79458/...
  • Их использование в качестве оболочек для функций, в некоторых случаях вам может понадобиться избежать повторения, например... func(FOO, "FOO");, вы можете определить макрос, который расширяет для вас строку func_wrapper(FOO);
  • Если вы хотите манипулировать переменными в локальной области вызывающих, то указатель на указатель работает нормально нормально, но в некоторых случаях его меньше беспокоит использование макроса.
    (присвоения нескольким переменным, для каждого -пиксельные операции, это пример, который вы можете предпочесть макросу над функцией... хотя он все еще сильно зависит от контекста, так как функции inline могут быть опцией).

По общему признанию, некоторые из них полагаются на расширения компилятора, которые не являются стандартными C. Смысл в том, что у вас может быть меньше портативного кода или у него должно быть ifdef, поэтому они используются только в том случае, когда компилятор поддерживает.


Предотвращение создания нескольких аргументов

Отмечая это, поскольку это одна из наиболее распространенных причин ошибок в макросах (например, передача в x++, где макрос может увеличиваться несколько раз).

его можно написать макросы, которые избегают побочных эффектов с множественным экземпляром аргументов.

C11 Общий

Если вам нравится иметь макрос 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 для разных типов.

Ответ 7

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

Ответ 8

Добавление к этому ответу.

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

Также макросы имеют некоторые специальные инструменты, чем могут помочь с переносимостью программы на разных платформах.

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

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

Ответ 9

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

Ответ 10

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

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

Конкретный пример: вы хотите написать альтернативную версию стандартной функции strpbrk, которая будет принимать, а не явный список символов для поиска внутри другой строки, функцию (указатель на a), которая будет возвращать 0 до тех пор, пока символ не будет что проходит некоторый тест (определяемый пользователем). Одна из причин, по которой вы, возможно, захотите сделать это, - это использовать другие стандартные функции библиотеки: вместо предоставления явной строки, полной пунктуации, вы можете передать ctype.h "ispunct" вместо этого и т.д. Если "ispunct" был реализован только как макрос, это не сработает.

Есть много других примеров. Например, если ваше сравнение выполняется макросом, а не функцией, вы не можете передать его в stdlib.h 'qsort'.

Аналогичной ситуацией в Python является "печать" в версии 2 по сравнению с версией 3 (непропускаемая инструкция против пропускаемой функции).

Ответ 11

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

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

вот так

int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));

functionThatTakeLongTime будет оцениваться 5 раз, что может значительно снизить производительность