Чем полезны макросы C?

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

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

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

Редактировать:

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


person Jack Ryan    schedule 17.03.2009    source источник
comment
Препроцессор C не сложен, он очень и очень прост.   -  person Alex    schedule 29.04.2009
comment
Препроцессор C очень прост, но он делает ваш код очень и очень сложным. ;)   -  person weberc2    schedule 04.04.2013
comment
Связанные - stackoverflow.com/questions/650461/   -  person ChrisF    schedule 05.06.2013
comment
Я наткнулся на этот gcc.gnu.org/onlinedocs/cpp/Macros.html# Макросы, которые помогают мне понять макрос в c. Может это поможет другим   -  person pyk    schedule 04.07.2015
comment
@Ayxan Пожалуйста, не используйте inline code для выделения (вместо этого используйте жирный шрифт или курсив). При этом в названиях языков, таких как C, вообще нет необходимости делать ударение.   -  person user694733    schedule 13.12.2019


Ответы (18)


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

Похоже, это плохо отражается на именовании макросов. Я предполагаю, что вам не пришлось бы эмулировать препроцессор, если бы это был макрос 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 потребовало бы немного больше усилий.

person Pete Kirkham    schedule 17.03.2009
comment
Спасибо. Это полезно. Я вижу в этом правдоподобное использование для них. Он обеспечивает простую в использовании и понятную отладку, которую нелегко заменить функцией. - person Jack Ryan; 17.03.2009

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

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

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

Макросы

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

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

Вот список общих применений макросов и соответствующих функций в D:

  1. Определение буквальных констант:

    • Метод препроцессора C

      #define VALUE 5
      
    • Путь D

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

    • Метод 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;
      
  3. Настройка соглашений о вызове функций:

    • Метод C препроцессора

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

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

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

    • Метод 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.

person Brad Gilbert    schedule 18.03.2009
comment
Большинство подумает, что написанные вами способы D излишни. - person OTZ; 26.08.2010
comment
Мне кажется, что неприятные директивы препроцессора не нужны, а я (неохотно) программист на C. - person weberc2; 18.10.2012
comment
Позволяет ли D объявить функцию таким образом, что, если ее аргументы являются константами времени компиляции, компилятор вычислит замену результата на константу времени компиляции? Очевидно, что есть ограничения на то, что можно делать в такой функции, но было бы полезно, чтобы foo=bit_reverse(0x12345678); оценивался как foo=0x1E6A2C48, но foo=bit_reverse(bar); генерировал вызов функции. Для таких целей можно использовать макросы C с расширениями, созданными gcc, но это несколько неприятно. - person supercat; 17.06.2013
comment
@supercat Я думаю, он может выполнять постоянное сворачивание функции, если функция объявлена ​​как pure. - person Brad Gilbert; 19.06.2013
comment
@BradGilbert: Есть ли у него способ проверить, может ли он рассматривать что-либо как константу времени компиляции? Выполнение смены битов через (((x) & 1) << 31)) | ((x) & 2) << 29) ... не лучший подход, когда x не является константой, но наиболее эффективные подходы, когда x является переменной, не могут быть выражены в виде константных выражений. - person supercat; 19.06.2013
comment
@supercat Я действительно не следил за развитием d последние пару лет. - person Brad Gilbert; 20.06.2013

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

Они также очень полезны для написания «полиморфных» или «перегруженных» выражений типа «функция»; например макрос 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;}
...

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

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

person fortran    schedule 14.05.2009
comment
@AndrewC Я подумал, что это немного оффтоп ... Тогда восстановил! ;) - person fortran; 07.06.2013

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

  • Константы 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 } } }

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

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

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

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

person hdante    schedule 09.11.2011

Помните, что макросы (и препроцессор) появились с первых дней C. Раньше они были ЕДИНСТВЕННЫМ способом выполнения встроенных «функций» (потому что, конечно, встроенное ключевое слово появилось совсем недавно), и они все еще остаются единственный способ заставить что-то быть встроенным.

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

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

person Michael Kohne    schedule 17.03.2009

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

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

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

person Pontus Gagge    schedule 17.03.2009
comment
Почему для определения структур данных лучше использовать препроцессор, чем структуру? И, конечно же, момент, с которого вы начинаете определять классы и шаблоны с помощью препроцессора, - это момент, когда вы можете подумать об использовании C ++ или другого языка с поддержкой этих конструкций. - person Jack Ryan; 17.03.2009
comment
Зависит от того, какие степени свободы вы хотите иметь: макросы позволяют согласованно создавать целый ряд структур данных. И вы совершенно правы: как я уже писал, это крайняя мера. Но иногда приходится работать с имеющимися у вас инструментами. - person Pontus Gagge; 17.03.2009
comment
Фактически вы можете использовать структуры и другие конструкции, а затем добавить немного синтаксического сахара с помощью препроцессора, - person Andrea; 19.01.2011
comment
Вот сообщение о том, как использовать макросы C для более удобного чтения и управления использованием структуры данных. - Полиморфные структуры данных с использованием C макросы - coredump - person coredump; 05.11.2011

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

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

/ *
* Битовые значения.
* /
#define BIT_0 1
#define BIT_1 2
#define BIT_2 4
#define BIT_3 8
#define BIT_4 16 < br> #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)
# определить BIT_31 (1 ‹› 31)

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

#define BIT (x) (1 ‹★ (x))

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

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

person Niet the Dark Absol    schedule 09.11.2011

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

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

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

/* 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) {
    /* ... */
}

or

char *str = "Hello, world!";

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

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

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

/* 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!")) {
    /* ... */
}

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

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

person wefwefa3    schedule 13.10.2014
comment
Замечательные примеры! - person rolls; 07.02.2017

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

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

person Julien Roncaglia    schedule 17.03.2009

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

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

person Charles E. Grant    schedule 18.03.2009
comment
Я понимаю, что встраивание может быть полезно в качестве оптимизации, но я не могу понять, зачем для этого нужен препроцессор. Почему встроенное ключевое слово не всегда работает? Использование препроцессора для реального встраивания кажется обычным приемом, который лучше исправить, изменив компилятор. - person Jack Ryan; 18.03.2009
comment
За встраивание кода приходится платить. У компилятора есть несколько практических правил, позволяющих уравновесить выгоду (более быстрый код) и стоимость (более толстый код). Если эмпирическое правило в вашем случае неверно, макросы позволят вам убрать компилятор с дороги, чтобы получить желаемый результат. - person Charles E. Grant; 18.03.2009
comment
Есть вопросы обратной совместимости и исторической случайности. Существуют миллиарды строк кода «C», которые люди не хотят изменять, поэтому изменения в языке «C» на данном этапе должны быть довольно небольшими и иметь как можно более обратную совместимость. - person Charles E. Grant; 18.03.2009

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

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

person Arkaitz Jimenez    schedule 17.03.2009

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

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

person coredump    schedule 05.11.2011

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

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

person noober    schedule 20.11.2014

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

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

person sykora    schedule 17.03.2009

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

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

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

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

if (RUNNING_ON_VALGRIND)

скорее, чем

#if RUNNING_ON_VALGRIND

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

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

person Tim Post♦    schedule 17.03.2009

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

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

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

person itsmatt    schedule 17.03.2009

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

#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).

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

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

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

person äli    schedule 22.12.2017