Для чего нужны макросы C?


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

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

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

Edit:

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

18 69

18 ответов:

Я в конечном итоге должен помнить, что макрос и заменить его в моей голове, как я читаю.

это, кажется, плохо отражается на именовании макросов. Я бы предположил, что вам не придется эмулировать препроцессор, если бы это было log_function_entry() макрос.

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

обычно они должны быть, если они не должны работать на общих параметрах.

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

будет работать на любом типе с < оператора.

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

в C99 макросы также позволяют вызывать вариативные функции, такие как е

#define log_message(guard,format,...) \
   if (guard) printf("%s:%d: " format "\n", __FILE__, __LINE__,__VA_ARGS_);

log_message( foo == 7, "x %d", x)

в котором формат работает как printf. Если параметр guard имеет значение true, он выводит сообщение вместе с номером файла и строки, на которых было напечатано сообщение. Если бы это был вызов функции, он не знал бы файл и строку, из которой вы его вызвали, и используя vaprintf было бы немного больше работы.

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

скопировано с DigitalMars.com

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

макрос

макросы препроцессора добавляют мощные характеристики и гибкость к C. Но у них есть и обратная сторона:

  • макросы не имеют понятия области действия; они действительны от точки определения до конца источника. Они режут а валок поперек .H-файлы, вложенные код и т. д. Когда #include'ing десятки тысяч строк макроопределений, становится проблематичным, чтобы избежать непреднамеренных макрорасширений.
  • макросы неизвестны отладчику. Попытка отладки программы с символьными данными подрывается отладчиком, который знает только о расширении макросов, а не о самих макросах.
  • макросы делают невозможным маркирование исходного кода, так как более раннее изменение макроса может произвольно повторяться жетоны.
  • чисто текстовая основа макросов приводит к произвольному и непоследовательному использованию, что делает код с использованием макросов подверженным ошибкам. (Некоторые попытки решить эту проблему были введены с шаблонами в C++.)
  • макросы по-прежнему используются для восполнения недостатков в выразительных возможностях языка, таких как "обертки" вокруг заголовочных файлов.

вот перечисление общих применений для макросов и соответствующая функция в Д:

  1. определения литеральных констант:

    • The C Препроцессора Образом

      #define VALUE 5
      
    • The D путь

      const int VALUE = 5;
      
  2. создание списка значений или флагов:

    • The C Препроцессора Образом

      int flags:
      #define FLAG_X  0x1
      #define FLAG_Y  0x2
      #define FLAG_Z  0x4
      ...
      flags |= FLAG_X;
      
    • The D Путь

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
      FLAGS flags;
      ...
      flags |= FLAGS.X;
      
  3. настройка соглашений о вызове функций:

    • The C Препроцессора Образом

      #ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #endif
      #ifndef _CRTAPI2
      #define _CRTAPI2 __cdecl
      #endif
      
      int _CRTAPI2 func();
      
    • The D путь

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

      extern (Windows)
      {
          int onefunc();
          int anotherfunc();
      }
      
  4. простой универсальный Программирование:

    • The C Препроцессора Образом

      выбор функции для использования на основе подстановки текста:

      #ifdef UNICODE
      int getValueW(wchar_t *p);
      #define getValue getValueW
      #else
      int getValueA(char *p);
      #define getValue getValueA
      #endif
      
    • The D путь

      D включает объявления символов, которые являются псевдонимами других символов:

      version (UNICODE)
      {
          int getValueW(wchar[] p);
          alias getValueW getValue;
      }
      else
      {
          int getValueA(char[] p);
          alias getValueA getValue;
      }
      

есть больше примеров на сайт DigitalMars.

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

Они также очень полезны для записи выражений" function like", которые являются "полиморфными" или "перегруженными"; например, макрос max определяется как:

#define max(a,b) ((a)>(b)?(a):(b))

полезно для любого числового типа; и в C вы не могли напишите:

int max(int a, int b) {return a>b?a:b;}
float max(float a, float b) {return a>b?a:b;}
double max(double a, double b) {return a>b?a:b;}
...

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

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

макросы разрешить кому-то изменять поведение программы во время компиляции. Рассмотрим это:

  • константы C позволяют фиксировать поведение программы во время разработки
  • переменные C позволяют изменять поведение программы во время выполнения
  • C макросы позволяют изменять поведение программы во время компиляции

во время компиляции означает, что неиспользуемый код даже не будет входить в двоичный файл и что процесс сборки может изменять значения, как пока он интегрирован с препроцессором макросов. Пример: make ARCH=arm (предполагает пересылку определения макроса как cc-DARCH=arm)

простые примеры: (из пределов glibc.h, определите наибольшее значение long)

#if __WORDSIZE == 64
#define LONG_MAX 9223372036854775807L
#else
#define LONG_MAX 2147483647L
#endif

проверяет (используя #define __WORDSIZE) во время компиляции, если мы компилируем для 32 или 64 бит. С помощью multilib toolchain, используя параметры-m32 и-m64 может автоматически изменить размер бита.

(версия POSIX запрос)

#define _POSIX_C_SOURCE 200809L

запросы во время компиляции поддержка POSIX 2008. Стандартная библиотека может поддерживать многие (несовместимые) стандарты, но с этим определением она предоставит правильные прототипы функций (например: getline (), no gets () и т. д.). Если библиотека не поддерживает стандарт, она может выдать ошибку #во время компиляции, а не сбой во время выполнения, например.

(жестко заданный путь)

#ifndef LIBRARY_PATH
#define LIBRARY_PATH "/usr/lib"
#endif

определяет, во время компиляции a жесткий каталог. Например, можно изменить с помощью-DLIBRARY_PATH=/home/user / lib. Если бы это был const char *, как бы вы настроили его во время компиляции ?

(pthread.h, сложные определения во время компиляции)

# define PTHREAD_MUTEX_INITIALIZER \
  { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }

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

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

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

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

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

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

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

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

макросы препроцессора фактически предлагают Turing-complete язык выполняются во время компиляции. Один из впечатляющих (и немного страшных) примеров этого находится на стороне C++:Увеличить Препроцессора библиотека использует C99/C++98 препроцессор для построения (относительно) безопасных программных конструкций, которые затем расширяются до независимо от того, какие базовые объявления и код вы вводите, будь то C или c++.

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

с Компьютер Благоглупости:

Я видел этот фрагмент кода во многих бесплатных игровых программах для UNIX:

/*
* Битовое значение.
* /
#определить БИТ_0 1
#определить БИТ_1 2
#define BIT_2 4
#define BIT_3 8
#define BIT_4 16
#define BIT_5 32
#define BIT_6 64
#define BIT_7 128
#define BIT_8 256
#define BIT_9 512
#define BIT_10 1024
#define BIT_11 2048
#define BIT_12 4096
#define BIT_13 8192
#define BIT_14 16384
#define BIT_15 32768
#define BIT_16 65536
#define BIT_17 131072
#define BIT_18 262144
#define BIT_19 524288
#define BIT_20 1048576
#define BIT_21 2097152
#define BIT_22 4194304
#define BIT_23 8388608
#определить BIT_24 16777216
#define BIT_25 33554432
#define BIT_26 67108864
#define BIT_27 134217728
#define BIT_28 268435456
#define BIT_29 536870912
#define BIT_30 1073741824
#define BIT_31 2147483648

гораздо более простой способ достичь этого:

#define BIT_0 0x00000001
#define BIT_1 0x00000002
#define BIT_2 0x00000004
#define BIT_3 0x00000008
#определять BIT_4 0x00000010
...
#define BIT_28 0x10000000
#define BIT_29 0x20000000
#define BIT_30 0x40000000
#define BIT_31 0x80000000

еще более простой способ-позволить компилятору выполнять вычисления:

#define BIT_0 (1)
#define BIT_1 (1 #define BIT_2 (1 #define BIT_3 (1 #define BIT_4 (1 ...
#define BIT_28 (1 #define BIT_29 (1 #define BIT_30 (1 #define BIT_31 (1

но зачем тратить время на определение 32 констант? Язык C также имеет параметризованные макросы. Все, что вам действительно нужно, это:

#определить бит (x) (1

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

Это только один из возможных способов использования Макросы.

один из случаев, когда макросы действительно сияют, когда они создают с ними код.

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

Я добавлю к тому, что уже было сказано.

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

вот несколько случаев, когда макросы могут быть очень полезны:

/* Get the number of elements in array 'A'. */
#define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))

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

int main(void)
{
    int a[] = {1, 2, 3, 4, 5};
    int i;
    for (i = 0; i < ARRAY_LENGTH(a); ++i) {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return 0;
}

здесь это не имеет значения, если другой программист добавляет еще пять элементов в a в декларацию. Элемент for-цикл всегда перебрать все элементы.

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

вы пишете:

char *str = "Hello, world!";

if (strcmp(str, "Hello, world!") == 0) {
    /* ... */
}

или

char *str = "Hello, world!";

if (!strcmp(str, "Hello, world!")) {
    /* ... */
}

чтобы проверить, если str указывает на "Hello, world". Я лично думаю, что оба эти решения выглядят довольно уродливо и запутанно (особенно !strcmp(...)).

вот два аккуратные макросы некоторые люди (в том числе я) используют, когда им нужно сравнить строки или память с помощью strcmp/memcmp:

/* Compare strings */
#define STRCMP(A, o, B) (strcmp((A), (B)) o 0)

/* Compare memory */
#define MEMCMP(A, o, B) (memcmp((A), (B)) o 0)

теперь вы можете написать код следующим образом:

char *str = "Hello, world!";

if (STRCMP(str, ==, "Hello, world!")) {
    /* ... */
}

вот намерение намного яснее!

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

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

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

В отличие от обычных функций, вы можете управлять потоком (if, while, for,...) в макросах. Вот пример:

#include <stdio.h>

#define Loop(i,x) for(i=0; i<x; i++)

int main(int argc, char *argv[])
{
    int i;
    int x = 5;
    Loop(i, x)
    {
        printf("%d", i); // Output: 01234
    } 
    return 0;
} 

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

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

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

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

например (реальный код, синтаксис компилятора 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 (как я did) и забудьте сделать то же самое со строкой (как и я), скрипт будет работать так, как вы не ожидаете (на самом деле, в моем примере это зависит от того, забыли ли вы переименовать поле в отдельный файл скрипта, но если скрипт используется для сериализации, это будет 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). Так что используйте его с умом. Если вы видите другой способ избавиться от копируемого кода (например, функции), используйте этот способ. Если вы видите, что избавление от копируемого кода с помощью макросов не стоит результата, сохраните копируемый код.

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

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

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

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

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

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

if (RUNNING_ON_VALGRIND)

, а не

#if RUNNING_ON_VALGRIND

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

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

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

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

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

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

#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, насколько я знаю).

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

когда это уместно использовать их?

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