Для чего нужны макросы C? - PullRequest
71 голосов
/ 17 марта 2009

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

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

Почему такой сложный препроцессор был введен для C ? И есть ли у кого-нибудь пример его использования, который поможет мне понять, почему он все еще используется не для простых условных компиляций в стиле #debug?

Edit:

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

Ответы [ 18 ]

4 голосов
/ 18 марта 2009

Учитывая комментарии в вашем вопросе, вы можете не в полной мере оценить, что вызов функции может повлечь за собой значительные издержки. Параметры и регистры ключей, возможно, придется скопировать в стек при входе, а стек развернуть при выходе. Это было особенно верно для старых чипов Intel. Макросы позволяли программисту сохранять абстракцию функции (почти), но избегали дорогостоящих накладных расходов при вызове функции. Ключевое слово inline носит рекомендательный характер, но компилятор не всегда понимает его правильно. Слава и опасность «C» в том, что вы обычно можете согнуть компилятор по своей воле.

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

3 голосов
/ 17 марта 2009

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

2 голосов
/ 05 ноября 2011

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

Подробное объяснение того, как использовать макросы для управления структурой данных, приведено здесь - http://multi -core-dump.blogspot.com / 2010/11 / интересно-использование-с-макросы-polymorphic.html

2 голосов
/ 20 ноября 2014

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

Например (реальный код, синтаксис компилятора VS 2010):

for each (auto entry in entries)
{
        sciter::value item;
        item.set_item("DisplayName",    entry.DisplayName);
        item.set_item("IsFolder",       entry.IsFolder);
        item.set_item("IconPath",       entry.IconPath);
        item.set_item("FilePath",       entry.FilePath);
        item.set_item("LocalName",      entry.LocalName);
        items.append(item);
    }

Это место, где вы передаете значение поля с тем же именем в обработчик сценариев. Это копия вставлена? Да. DisplayName используется в качестве строки для скрипта и в качестве имени поля для компилятора. Это плохо? Да. Если вы реорганизуете код и переименовываете LocalName в RelativeFolderName (как я сделал) и забываете делать то же самое со строкой (как я), скрипт будет работать так, как вы этого не ожидаете (на самом деле, в моем примере это зависит от того, забыли ли вы переименовать поле в отдельном файле сценария, но если сценарий используется для сериализации, это будет ошибка на 100%).

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

for each (auto entry in entries)
{
#define STR_VALUE(arg) #arg
#define SET_ITEM(field) item.set_item(STR_VALUE(field), entry.field)
        sciter::value item;
        SET_ITEM(DisplayName);
        SET_ITEM(IsFolder);
        SET_ITEM(IconPath);
        SET_ITEM(FilePath);
        SET_ITEM(LocalName);
#undef SET_ITEM
#undef STR_VALUE
        items.append(item);
    }

К сожалению, это открывает двери для других типов ошибок. Вы можете сделать опечатку при написании макроса и никогда не увидите испорченный код, потому что компилятор не показывает, как он выглядит после всей предварительной обработки. Кто-то другой может использовать то же имя (поэтому я «выпускаю» макросы как можно скорее с #undef). Так что используйте это с умом. Если вы видите другой способ избавиться от кода, вставленного копией (такого как функции), используйте этот способ. Если вы видите, что избавление от кода с вставленной копией с помощью макросов не стоит результата, сохраняйте код с вставленной копией.

1 голос
/ 17 марта 2009

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

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

0 голосов
/ 17 марта 2009

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

#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

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

0 голосов
/ 22 декабря 2017

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

#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

Как правило, рекомендуется избегать использования макросов, когда в этом нет необходимости, по многим причинам удобочитаемость является главной проблемой. Итак:

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

Почти никогда, так как есть более читаемая альтернатива, которая inline, см. https://www.greenend.org.uk/rjk/tech/inline.html или http://www.cplusplus.com/articles/2LywvCM9/ (вторая ссылка - страница C ++, но, насколько мне известно, эта точка применима к компиляторам c).

Теперь, небольшая разница в том, что макросы обрабатываются препроцессором, а встроенные - компилятором, но в настоящее время практических отличий нет.

когда это целесообразно использовать?

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

0 голосов
/ 17 марта 2009

Макросы .. когда ваш & # (* $ & компилятор просто отказывается что-то встроить.

Это должен быть мотивационный плакат, нет?

На полном серьезе, Google злоупотребление препроцессором (вы можете увидеть такой же вопрос SO, как результат # 1). Если я пишу макрос, который выходит за рамки функциональности assert (), я обычно пытаюсь увидеть, действительно ли мой компилятор встроит аналогичную функцию.

Другие будут возражать против использования #if для условной компиляции .. они предпочтут вас:

if (RUNNING_ON_VALGRIND)

, а не

#if RUNNING_ON_VALGRIND

.. для целей отладки, поскольку вы можете видеть if (), но не #if в отладчике. Затем мы погрузимся в #ifdef vs # if.

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

...