Для чего нужны macros C?
Я написал немного C , и я могу прочитать его достаточно хорошо, чтобы получить общее представление о том, что он делает, но каждый раз, когда я сталкивался с макросом, он полностью меня бросил. Я в конечном итоге должен помнить, что такое макрос, и заменить его в моей голове, когда я читаю. Те, с которыми я столкнулся, были интуитивными и понятными, всегда были маленькими мини-функциями, поэтому я всегда задавался вопросом, почему они были не просто функциями.
Я могу понять необходимость определения различных типов сборки для отладки или кросс-платформенных сборок в препроцессоре, но способность определять произвольные подстановки, по-видимому, полезна только для того, чтобы сделать уже сложный язык еще труднее понять.
Почему такой сложный препроцессор, введенный для C ? И есть ли у кого-нибудь пример его использования, который заставит меня понять, почему он все еще используется для целей, отличных от простых, если условные компиляции типа #debug?
- Почему шаблон функции не может быть частично специализированным?
- Почему я не могу наследовать от int в C ++?
- Почему ссылки не переустанавливаются в C ++
- Почему в C # не разрешены параметры const?
- Как работает бесуровневый язык?
Редактировать:
Прочитав ряд ответов, я до сих пор не понимаю. Самый общий ответ – встроенный код. Если ключевое слово inline не делает этого, то либо у него есть веская причина не делать этого, либо для исправления реализации. Я не понимаю, почему нужен совершенно другой механизм, который означает «действительно встроить этот код» (кроме того, что код, написанный до того, как был встроен). Я также не понимаю идею, что было упомянуто, что «если ее слишком глупо, чтобы быть включенным в функцию». Разумеется, любая функция кода, которая принимает входные данные и выводит результат, лучше всего вводить в функцию. Я думаю, что я не могу получить его, потому что я не привык к микро-оптимизации написания C , но препроцессор просто чувствует себя как сложное решение нескольких простых проблем.
- C # static member "inheritance" - почему это вообще существует?
- Ограничение дженериков с помощью ключевого слова 'super'
- C ++: обоснование правила скрытия
- Почему C ++ нужен отдельный заголовочный файл?
- Почему ваш тип данных оператора switch не может быть длинным, Java?
- Почему частные поля являются приватными для типа, а не экземпляра?
- Перегрузка функций по типу возврата?
- Я не понимаю, зачем нам нужно «новое» ключевое слово
Я в конечном итоге должен помнить, что такое макрос, и заменить его в моей голове, когда я читаю.
Это, по-видимому, плохо отражает наименования макросов. Я бы предположил, что вам не нужно будет эмулировать препроцессор, если бы это был log_function_entry()
.
Те, с которыми я столкнулся, были интуитивно понятны и понятны, всегда были маленькими мини-функциями, поэтому я всегда задавался вопросом, почему они просто не работают.
Обычно они должны быть, если только им не нужно работать с общими параметрами.
#define max(a,b) ((a)<(b)?(b):(a))
будет работать на любом типе с помощью оператора <
.
Более того, только функции, macros позволяют выполнять операции с использованием символов в исходном файле. Это означает, что вы можете создать новое имя переменной или ссылаться на исходный файл и номер строки, в котором находится макрос.
В C99 macros также позволяют вам вызывать переменные функции, такие как 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
было бы немного больше работы.
Этот отрывок в значительной степени подводит итог моему мнению по этому вопросу, сравнивая несколько способов использования макросов C
и как их реализовать в D
скопировано из DigitalMars.com
Назад, когда
C
был изобретен, технология компилятора была примитивна. Установка препроцессора текстового макроса на переднем конце была простым и легким способом добавления множества мощных функций. Возрастающий размер и сложность программ показали, что эти функции имеют множество неотъемлемых проблем.D
не имеет препроцессора; ноD
обеспечивает более масштабируемое средство для решения тех же задач.
макрос
Макросы препроцессора добавляют к 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 представлено больше примеров.
Они являются языком программирования (более простым) на вершине 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;} ...
даже если вы этого захотите, потому что вы не можете перегружать функции.
И не говоря уже об условной компиляции и вложенных файлах (которые также являются частью языка макросов) …
Макросы позволяют кому-то изменять поведение программы во время компиляции. Учти это:
- 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 } } }
Большие fragmentы текста, которые в противном случае не были бы упрощены, могут быть объявлены (всегда во время компиляции). Это невозможно сделать с помощью функций или констант (во время компиляции).
Чтобы не усложнять ситуацию и не предлагать стили плохого кодирования, я не буду приводить пример кода, который компилируется в разных, несовместимых операционных системах. Для этого используйте свою систему кросс-сборки, но должно быть ясно, что препроцессор позволяет это без помощи системы сборки, не прерывая компиляцию из-за отсутствия интерфейсов.
Наконец, подумайте о важности условной компиляции встроенных систем, где скорость процессора и память ограничены, а системы очень неоднородны.
Теперь, если вы спросите, возможно ли заменить все определения констант макросов и вызовы функций правильными определениями? Ответ – да, но это не будет просто необходимость менять поведение программы во время компиляции. Препроцессор по-прежнему будет необходим.
Помните, что macros (и предварительный процессор) происходят с самых ранних дней C. Они были ТОЛЬКО способом делать встроенные «функции» (потому что, конечно, inline – очень недавнее ключевое слово), и они все еще являются только способ заставить что-то быть встроенным.
Кроме того, macros – это единственный способ сделать такие трюки, как вставка файла и строки в строковые константы во время компиляции.
В наши дни многие из того, что macros были единственным способом сделать, лучше обрабатывать с помощью новых механизмов. Но они все еще имеют свое место, время от времени.
Помимо включения для эффективности и условной компиляции, macros могут использоваться для повышения уровня абстракции низкоуровневого кода C. C на самом деле не изолирует вас от подробных подробностей управления памятью и ресурсами и точного компоновки данных, а также поддерживает очень ограниченные формы скрытия информации и другие механизмы управления большими системами. С помощью макросов вы больше не ограничены использованием только базовых конструкций на языке C: вы можете определить свои собственные структуры данных и конструкции кодирования (включая classы и шаблоны!), В то же время номинально записывая C!
Макросы препроцессора фактически предлагают язык завершения Turing, выполняемый во время компиляции. Один из впечатляющих (и немного пугающих) примеров этого выше на стороне C ++: библиотека Preostcessor Boost использует препроцессор C99 / C ++ 98 для создания (относительно) безопасных конструкций программирования, которые затем расширяются до любых базовых деклараций и кода вы вводите, C или C ++.
На практике я бы рекомендовал программирование препроцессора в качестве крайней меры, когда у вас нет широты, чтобы использовать конструкции высокого уровня в более безопасных языках. Но иногда хорошо знать, что вы можете сделать, если спина у стены и ласки закрываются …!
От компьютерных глупостей :
Я видел этот fragment кода во многих бесплатных игровых программах для 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 также имеет параметризованные macros. Все, что вам действительно нужно:
#define BIT (x) (1 << (x))
В любом случае, интересно, парень, который написал оригинальный код, использовал калькулятор или просто вычислил все это на бумаге.
Это всего лишь одно возможное использование макросов.
Один из случаев, когда macros действительно сияют, – это при создании кода с ними.
Раньше я работал на старой C ++-системе, которая использовала систему плагинов своим способом передать параметры плагину (используя настраиваемую структуру, подобную карте). Некоторые простые macros были использованы, чтобы иметь возможность справиться с этой причудой и позволили нам использовать реальные C ++ classы и функции с нормальными параметрами в плагинах без особых проблем. Весь код клея генерируется макросами.
Я добавлю к уже сказанному.
Поскольку macros работают над текстовыми подстановками, они позволяют делать очень полезные вещи, которые невозможно было бы использовать с помощью функций.
Здесь несколько случаев, когда macros могут быть действительно полезными:
/* 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; }
Здесь не имеет значения, добавляет ли еще один программист еще пять элементов к объявлению. Для -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!")) { /* ... */ }
Это намерение гораздо яснее!
Это случаи, когда macros используются для того, что функции не могут выполнить. Макросы не должны использоваться для замены функций, но у них есть другие хорошие применения.
Учитывая комментарии в вашем вопросе, вы не можете в полной мере оценить, что вызов функции может повлечь за собой значительную часть накладных расходов. Параметры и регистры ключей, возможно, придется копировать в стек по пути внутрь, а стека разматывать на выходе. Это особенно верно для старых чипов Intel. Макросы позволяют программисту сохранять абстракцию функции (почти), но избегают дорогостоящих накладных расходов на вызов функции. Ключевое слово inline является рекомендательным, но компилятор может не всегда правильно это понимать. Слава и опасность «C» – это то, что вы обычно можете сгибать компилятор по своему усмотрению.
В вашем хлебе и маслах повседневное программирование такого типа микро-оптимизации (избегая вызовов функций) обычно хуже, чем бесполезно, но если вы пишете критическую по времени функцию, называемую kernelм операционной системы, то это может иметь огромное значение.
В отличие от обычных функций, вы можете выполнять stream управления (if, while, for, …) в макросах. Вот пример:
#include #define Loop(i,x) for(i=0; i
Используя текстовую манипуляцию с препроцессором C, можно построить эквивалент C полиморфной структуры данных. Используя этот метод, мы можем построить надежный инструментарий примитивных структур данных, который можно использовать в любой программе на C, поскольку они используют синтаксис C, а не специфику какой-либо конкретной реализации.
Подробное объяснение того, как использовать macros для управления структурой данных, приведено здесь: http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html
Это полезно для наложения кода и предотвращения служебных вызовов функций. Также, используя его, если вы хотите изменить поведение позже, не редактируя множество мест. Это не полезно для сложных вещей, но для простых строк кода, которые вы хотите встроить, это неплохо.
Макросы позволяют избавляться от копируемых fragmentов, которые вы не можете устранить каким-либо другим способом.
Например (реальный код, синтаксис компилятора 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); }
К сожалению, это открывает дверь для других типов ошибок. Вы можете написать опечатку с макросом и никогда не увидите испорченный код, потому что компилятор не показывает, как он выглядит после всей предварительной обработки. Кто-то другой может использовать одно и то же имя (вот почему я «выпускаю» macros ASAP с помощью #undef
). Поэтому используйте его с умом. Если вы видите другой способ избавиться от скопированного кода (например, функций), используйте этот способ. Если вы видите, что избавление от скопированного кода с помощью макросов не стоит результата, сохраните код с копированием.
Одна из очевидных причин заключается в том, что с помощью макроса код будет расширен во время компиляции, и вы получите псевдо-функциональный вызов без служебных накладных.
В противном случае вы также можете использовать его для символических констант, так что вам не нужно редактировать одно и то же значение в нескольких местах, чтобы изменить одну мелочь.
Макросы .., когда ваш & # (* $ & компилятор просто отказывается встраивать что-то.
Это должен быть мотивационный плакат, нет?
По всей серьезности, злоупотребление препроцессором Google (вы можете увидеть аналогичный вопрос SO как результат №1). Если я пишу макрос, который выходит за frameworks функции 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 ++, но эта точка применима к компиляторам, насколько я знаю).
Теперь небольшая разница заключается в том, что macros обрабатываются предварительным процессором, а inline обрабатывается компилятором, но в настоящее время нет никакой практической разницы.
когда это целесообразно использовать?
Для небольших функций (макс. Два или три лайнера). objective состоит в том, чтобы получить преимущество в течение времени выполнения программы, поскольку такие функции, как macros (и встроенные функции), являются заменами кода, выполняемыми во время предварительной обработки (или компиляции в случае встроенного) и не являются реальными функциями, живущими в памяти, поэтому нет накладных расходов на функции (более подробная информация на связанных страницах).