# undef-in на практике? - PullRequest
       25

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

15 голосов
/ 19 октября 2008

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

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

#undef getchar

int getchar(void) { ... }

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

Ответы [ 8 ]

15 голосов
/ 20 октября 2008

Что он делает

Если вы прочитаете Стандартную библиотеку Плаугера (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 вызывает макрос.

14 голосов
/ 20 октября 2008

Макросы часто используются для генерации большого количества кода. Это часто довольно локализованное использование, и можно #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
6 голосов
/ 20 октября 2008

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

2 голосов
/ 20 октября 2008

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

2 голосов
/ 19 октября 2008

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

1 голос
/ 20 октября 2008

В дополнение к исправлению проблем с макросами, загрязняющими глобальное пространство имен, еще одно использование #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"

/* ------------------------------------------------------ */
1 голос
/ 20 октября 2008

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

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

0 голосов
/ 20 октября 2008

Если макрос может быть защищен, должен быть механизм для отмены.

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

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

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

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

...