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

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

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

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

Edit:

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

Macros

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

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

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

  • Определение констант литерала:

    • Путь C препроцессора

      #define VALUE 5
      
    • D Путь

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

    • Путь C препроцессора

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

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
      FLAGS flags;
      ...
      flags |= FLAGS.X;
      
  • Соглашения о назначении функций функции:

    • Путь C препроцессора

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

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

      extern (Windows)
      {
          int onefunc();
          int anotherfunc();
      }
      
  • Простое общее программирование:

    • Путь C препроцессора

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

#define _POSIX_C_SOURCE 200809L

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

(жесткий путь)

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

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

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

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

Из Компьютерные глупости:

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

/*
 * Битовые значения.
 */
 #define BIT_0 1
 #define BIT_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
 #define 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
 #define 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 < 1)
 #define BIT_2 (1 < 2)
 #define BIT_3 (1 < 3)
 #define BIT_4 (1 < 4)
 ...
 #define BIT_28 (1 < 28)
 #define BIT_29 (1 < 29)
 #define BIT_30 (1 < 30)
 #define BIT_31 (1 < 31)

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

#define BIT (x) (1 < (x))

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

Это всего лишь одно возможное использование макросов.

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

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

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

/* 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 -loop будет всегда проходить через все элементы.

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

Вы пишете:

char *str = "Hello, world!";

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

или

char *str = "Hello, world!";

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

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

Вот два аккуратных макроса, которые некоторые люди (включая I) используют, когда им нужно сравнивать строки или память с помощью 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!")) {
    /* ... */
}

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

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

Ответ 11

В отличие от обычных функций, вы можете управлять потоком управления (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;
} 

Ответ 12

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

Ответ 13

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

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

Ответ 14

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

Например (реальный код, синтаксис компилятора 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);
    }

Это место, где вы передаете значение поля под тем же именем в движок script. Скопировано ли это? Да. DisplayName используется как строка для script и как имя поля для компилятора. Это плохо? Да. Если вы реорганизуете код и переименуете LocalName в RelativeFolderName (как и я) и забудьте сделать то же самое со строкой (как и я), script будет работать так, как вы не ожидаете (в факт, в моем примере это зависит от того, забыли ли вы переименовать поле в отдельный файл script, но если для сериализации используется script, это будет 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);
    }

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

Ответ 15

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

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

Ответ 16

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

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

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

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

if (RUNNING_ON_VALGRIND)

а не

#if RUNNING_ON_VALGRIND

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

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

Ответ 17

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

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

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

Ответ 18

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

#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, насколько мне известно).

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

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

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