Вопрос
Меня интересует практическое использование #undef в C.Я работаю через K&R, и до препроцессора.Большую часть этого материала я (более или менее) понял, но кое-что на странице 90 (второе издание) меня зацепило:
Имена могут быть неопределенными с помощью
#undef
, как правило, чтобы убедиться, что рутина действительно функция, а не макро:
#undef getchar
int getchar(void) { ... }
Это обычная практика защищаться от кого-то? #define
-создать макрос с тем же именем, что и у вашей функции?Или это действительно скорее образец, которого не было бы в реальности?(Например, никто в своем правильном, неправильном или безумном уме не должен переписывать getchar()
, поэтому оно не должно возникать.) Чувствуете ли вы необходимость сделать это, имея собственные имена функций?Изменится ли это, если вы разрабатываете библиотеку для использования другими?
Решение
Что оно делает
Если вы читаете Плаугера Стандартная библиотека C (1992), вы увидите, что <stdio.h>
в заголовке разрешено указывать getchar()
и getc()
как функциональные макросы (со специальным разрешением для getc()
чтобы оценить аргумент указателя файла более одного раза!).Однако, даже если она предоставляет макросы, реализация также обязана предоставлять реальные функции, выполняющие ту же работу, в первую очередь для того, чтобы вы могли получить доступ к указателю функции, называемому getchar()
или getc()
и передать это другим функциям.
То есть, делая:
#include <stdio.h>
#undef getchar
extern int some_function(int (*)(void));
int core_function(void)
{
int c = some_function(getchar);
return(c);
}
Как написано, core_function()
довольно бессмысленно, но это иллюстрирует суть.Вы можете сделать то же самое с isxxxx()
макросы в <ctype.h>
тоже, например.
Обычно вы не хотите этого делать — обычно вы не хотите удалять определение макроса.Но когда вам понадобится реальная функция, вы можете ее получить.Люди, предоставляющие библиотеки, могут с успехом имитировать функциональность стандартной библиотеки C.
Редко требуется
Также обратите внимание, что одна из причин, по которой вам редко нужно использовать явный #undef
потому что вы можете вызвать функцию вместо макроса, написав:
int c = (getchar)();
Поскольку токен после getchar
не является (
, это не вызов функционального макроса, поэтому он должен быть ссылкой на функцию.Аналогично, первый пример выше будет корректно компилироваться и работать даже без #undef
.
Если вы реализуете свою собственную функцию с переопределением макроса, вы можете использовать это с хорошим эффектом, хотя это может немного сбивать с толку, если не объяснено.
/* function.h */
…
extern int function(int c);
extern int other_function(int c, FILE *fp);
#define function(c) other_function(c, stdout);
…
/* function.c */
…
/* Provide function despite macro override */
int (function)(int c)
{
return function(c, stdout);
}
Строка определения функции не вызывает макрос, поскольку токен после function
не является (
.А return
строка вызывает макрос.
Другие советы
Макросы часто используются для генерации большого количества кода.Часто это довольно локализованное использование, и его можно безопасно использовать. #undef
любые вспомогательные макросы в конце конкретного заголовка, чтобы избежать конфликтов имен, поэтому в другое место импортируется только фактически сгенерированный код, а макросы, используемые для генерации кода, — нет.
/Редактировать:В качестве примера я использовал это для создания структур.Ниже приведен отрывок из реального проекта:
#define MYLIB_MAKE_PC_PROVIDER(name) \
struct PcApi##name { \
many members …
};
MYLIB_MAKE_PC_PROVIDER(SA)
MYLIB_MAKE_PC_PROVIDER(SSA)
MYLIB_MAKE_PC_PROVIDER(AF)
#undef MYLIB_MAKE_PC_PROVIDER
Потому что препроцессор #define
все находятся в одном глобальном пространстве имен, легко могут возникнуть конфликты пространств имен, особенно при использовании сторонних библиотек.Например, если вы хотите создать функцию с именем OpenFile
, он может скомпилироваться неправильно, поскольку файл заголовка <windows.h>
определяет токен OpenFile
сопоставить либо OpenFileA
или OpenFileW
(в зависимости от того, если UNICODE
определено или нет).Правильное решение состоит в том, чтобы #undef
OpenFile
прежде чем определять свою функцию.
Я использую его только тогда, когда макрос в #included
файл мешает одной из моих функций (например, он имеет то же имя).Затем я #undef
макрос, чтобы я мог использовать свою собственную функцию.
Хотя я думаю, что Джонатан Леффлер дал вам правильный ответ.Вот очень редкий случай, когда я использую #undef.Обычно макрос можно повторно использовать внутри многих функций;поэтому вы определяете его в верхней части файла или в файле заголовка.Но иногда внутри функции есть повторяющийся код, который можно сократить с помощью макроса.
int foo(int x, int y)
{
#define OUT_OF_RANGE(v, vlower, vupper) \
if (v < vlower) {v = vlower; goto EXIT;} \
else if (v > vupper) {v = vupper; goto EXIT;}
/* do some calcs */
x += (x + y)/2;
OUT_OF_RANGE(x, 0, 100);
y += (x - y)/2;
OUT_OF_RANGE(y, -10, 50);
/* do some more calcs and range checks*/
...
EXIT:
/* undefine OUT_OF_RANGE, because we don't need it anymore */
#undef OUT_OF_RANGE
...
return x;
}
Чтобы показать читателю, что этот макрос полезен только внутри функции, в конце он не определен.Я не хочу никого поощрять использовать такие хакерские макросы.Но если вам нужно, #undef их в конце.
Это обычная практика защиты от того, чтобы кто-то #определял макрос с тем же именем, что и ваша функция?Или это действительно скорее образец, которого не было бы в реальности?(Например, никто в своем здравом, неправомерном или безумном уме не должен переписывать getchar(), поэтому он не должен возникать.)
Немного того и другого.Хороший код не потребует использования #undef
, но существует много плохого кода, с которым вам придется работать. #undef
может оказаться неоценимым, когда кто-то проделывает такой трюк, как #define bool int
.
Помимо исправления проблем с макросами, загрязняющими глобальное пространство имен, предусмотрено еще одно использование #undef
это ситуация, когда макросу может потребоваться иметь разное поведение в разных местах.Это не совсем распространенный сценарий, но на ум приходят следующие пара:
тот
assert
Определение макроса может быть изменено в середине модуля компиляции для случая, когда вы можете захотеть выполнить отладку одной части вашего кода, но не других.В дополнение кassert
себе нужно быть#undef
пришлось сделать это,NDEBUG
макрос необходимо переопределить, чтобы перенастроить желаемое поведениеassert
Я видел метод, используемый для обеспечения того, чтобы глобальные переменные определялись ровно один раз, с помощью макроса, объявляющего переменные как
extern
, но макрос будет переопределен впустую для единственного случая, когда заголовок/объявления используются для определения переменных.
Что-то вроде (я не говорю, что это обязательно хороший метод, просто я видел такой метод):
/* globals.h */
/* ------------------------------------------------------ */
#undef GLOBAL
#ifdef DEFINE_GLOBALS
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL int g_x;
GLOBAL char* g_name;
/* ------------------------------------------------------ */
/* globals.c */
/* ------------------------------------------------------ */
#include "some_master_header_that_happens_to_include_globals.h"
/* define the globals here (and only here) using globals.h */
#define DEFINE_GLOBALS
#include "globals.h"
/* ------------------------------------------------------ */
Если макрос можно определить, должна быть возможность отменить определение.
трекер памяти, который я использую, определяет свои собственные макросы создания/удаления для отслеживания информации о файле/строке.этот макрос нарушает SC++L.
#pragma push_macro( "new" )
#undef new
#include <vector>
#pragma pop_macro( "new" )
Что касается вашего более конкретного вопроса:Пространства имен часто эмулируются в C путем добавления к библиотечным функциям идентификатора.
Слепое отмена определения макросов приведет к путанице, ухудшит удобство обслуживания и может сломать вещи, которые полагаются на исходное поведение.Если вас заставили, по крайней мере, используйте push/pop, чтобы сохранить исходное поведение везде.