Вопрос

Меня интересует практическое использование #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, чтобы сохранить исходное поведение везде.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top