# undef-ing на практике?

Мне интересно о практическом использовании #undef в C. Я работаю через K & R, и я до препроцессора. Большинство из них было материалом я (более или менее) понятым, но что-то на странице 90 (второе издание) торчали у меня:

Имена могут быть undefined с #undef, обычно для обеспечения того, чтобы действительно функция, а не макрос:

#undef getchar

int getchar(void) { ... }

Это обычная практика защиты от кого-то #define - использование макроса с тем же именем, что и ваша функция? Или это действительно больше образец, который не произошел бы на самом деле? (Э.Г., никто в своем праве, неправильный или безумный разум не должен переписываться getchar(), поэтому он не должен появляться.) С вашими собственными именами функций вы чувствуете необходимость сделать это? Это изменится, если вы разрабатываете библиотеку для других пользователей?

Ответы

Ответ 1

Что он делает

Если вы читаете Plauger The Standard C Library (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 вызывает макрос.

Ответ 2

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

/Edit: В качестве примера я использовал это для создания структур для меня. Ниже приведен фрагмент фактического проекта:

#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

Ответ 3

Поскольку препроцессор #define находится в одном глобальном пространстве имен, легко создавать конфликты пространства имен, особенно при использовании сторонних библиотек. Например, если вы хотите создать функцию с именем OpenFile, она может не скомпилироваться правильно, потому что заголовочный файл <windows.h> определяет токен OpenFile для сопоставления с OpenFileA или OpenFileW (в зависимости от того, если UNICODE определяется или нет). Правильное решение - #undef OpenFile перед определением вашей функции.

Ответ 4

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

Ответ 5

Хотя я думаю, что Джонатан Леффлер дал вам правильный ответ. Вот очень редкий случай, когда я использую #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;
}

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

Ответ 6

Это обычная практика защиты от кого-то # определения макроса с тем же именем, что и ваша функция? Или это действительно больше образец, который не произошел бы на самом деле? (ЭГ, никто в своем праве, неправильный или безумный разум не должен переписывать getchar(), поэтому он не должен появляться.)

Немного обоим. Хороший код не требует использования #undef, но там много плохого кода, с которым вы должны работать. #undef может оказаться неоценимым, если кто-то тянет трюк вроде #define bool int.

Ответ 7

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

  • макрос assert может изменить его определение в середине блока компиляции для случая, когда вы можете выполнить отладку на некоторой части вашего кода, но не на других. В дополнение к assert, который должен быть #undef 'ed для этого, макрос 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"

/* ------------------------------------------------------ */

Ответ 8

Если макрос может быть def'ed, должно быть средство для undef.

Используемый мной трекер памяти определяет его собственные новые/удаленные макросы для отслеживания информации о файле/строке. этот макрос разбивает SС++ L.

#pragma push_macro( "new" )
#undef new
#include <vector>
#pragma pop_macro( "new" )

Относительно вашего более конкретного вопроса: пространства имен часто emul, ated в C путем префикса библиотечных функций с идентификатором.

Слепые макрокоманды undefing собираются добавить путаницу, уменьшить ремонтопригодность и могут нарушить работу, основанную на первоначальном поведении. Если вы были вынуждены, по крайней мере, используйте push/pop, чтобы сохранить исходное поведение везде.