Когда использовать функциональные макросы в C - PullRequest
11 голосов
/ 24 октября 2009

Я читал некоторый код, написанный на C сегодня вечером файл был функциональным макросом HASH:

#define HASH(fp) (((unsigned long)fp)%NHASH)

Это заставило меня задуматься, почему кто-то решил реализовать функционировать таким образом, используя функциональный макрос вместо реализации это как обычная функция vanilla C? Каковы преимущества и недостатки каждой реализации?

Спасибо большое!

Ответы [ 6 ]

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

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

Это может показаться не таким уж большим. Но в вашем примере макрос превращается в 1-2 инструкции машинного языка, в зависимости от вашего процессора:

  • Получите значение fp из памяти и поместите его в регистр
  • Возьмите значение в регистре, выполните вычисление модуля (%) по фиксированному значению и оставьте его в том же регистре

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

  • Вставьте значение fp в стек
  • Вызов функции, которая также помещает следующий (возвращаемый) адрес в стек
  • Может быть, построить фрейм стека внутри функции, в зависимости от архитектуры процессора и соглашения ABI
  • Получить значение fp из стека и поместить его в регистр
  • Возьмите значение в регистре, выполните вычисление модуля (%) по фиксированному значению и оставьте его в том же регистре
  • Может быть, взять значение из регистра и вернуть его в стек, в зависимости от процессора и ABI
  • Если был создан кадр стека, разверните его
  • Извлечь адрес возврата из стека и возобновить выполнение там инструкций

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

Лично я предпочитаю использовать inline C ++ как более читаемый и менее подверженный ошибкам, но inline также действительно больше подсказывает компилятору, что он не должен принимать. Макросы препроцессора - кувалда, с которой не может спорить компилятор.

16 голосов
/ 24 октября 2009

Одним из важных преимуществ реализации на основе макросов является то, что она не привязана к какому-либо конкретному типу параметра. Функциональный макрос в C во многих отношениях действует как шаблонная функция в C ++ (шаблоны в C ++ родились как «более цивилизованные» макросы, кстати). В этом конкретном случае аргумент макроса не имеет конкретного типа. Это может быть абсолютно все, что можно преобразовать в тип unsigned long. Например, если пользователь этого пожелает (и если он готов принять последствия, определенные реализацией), он может передать в этот макрос типы указателей.

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

#define ABS(x) ((x) >= 0 ? (x) : -(x))

работает со всеми арифметическими типами, в то время как реализация на основе функций должна обеспечивать довольно много из них (я подразумеваю стандартные abs, labs, llabs и fabs). (И да, я знаю о традиционно упоминаемых опасностях такого макроса.)

Макросы не идеальны, но популярное изречение о том, что «функциональные макросы больше не нужны из-за встроенных функций», - просто чушь. Для полной замены функционально-подобных макросов C потребуются шаблоны функций (как в C ++) или, по крайней мере, перегрузка функций (как в C ++ снова). Без этого функциональные макросы являются и останутся чрезвычайно полезным основным инструментом в C.

3 голосов
/ 24 октября 2009

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

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

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

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

3 голосов
/ 24 октября 2009

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

С другой стороны, они иногда даже быстрее, чем static inline метод. Я сильно оптимизировал короткую программу и обнаружил, что вызов метода static inline занимает примерно вдвое больше времени (только накладные расходы, а не фактическое тело функции) по сравнению с макросом.

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

Ваш пример вообще не является функцией,

#define HASH(fp) (((unsigned long)fp)%NHASH)
//  this is a cast ^^^^^^^^^^^^^^^
//  this is your value 'fp'       ^^
//  this is a MOD operation          ^^^^^^

Я думаю, это был просто способ написания более читабельного кода с опцией приведения и модификации, заключенной в один макрос 'HASH(fp)'


Теперь, если вы решите написать для этого функцию, она, вероятно, будет выглядеть так:

int hashThis(int fp)
{
  return ((fp)%NHASH);
}

Совершенно избыточно для функции как таковой,

  • вводит точку вызова
  • представляет установку и восстановление стека вызовов
0 голосов
/ 24 октября 2009

Препроцессор C может использоваться для создания встроенных функций. В вашем примере появится код для вызова функции HASH, но вместо этого это просто встроенный код.

Преимущества выполнения макрофункций были устранены, когда в C ++ появились встроенные функции. Многие более старые API, такие как MFC и ATL, все еще используют макро-функции для выполнения приемов препроцессора, но это просто делает код сложным и трудным для чтения.

...