В чем смысл макроса PROTOTYPE, который просто расширяется до своих аргументов?

82

У меня есть файл заголовка, который содержит

#define PROTOTYPE(s) s

Какой в ​​этом смысл? Похоже, он просто заменит ввод самим собой.

Есть множество других директив вокруг него, но только один , который , как представляется , есть какой - либо подшипник только что проверил , если он определен: #ifndef PROTOTYPE. Я нашел несколько мест в заголовочных файлах HDF4 , которые делают это: #define PROTOTYPE. Так что ничего из этого не проясняет мой вопрос. По-прежнему кажется довольно бесполезным.

Вот как это используется:

CS_RETCODE clientmsg_callback PROTOTYPE((
CS_CONTEXT * context,
CS_CONNECTION *connection,
CS_CLIENTMSG *clientmsg));

Это часть проекта, в котором используется Sybase Open Client. clientmsg_callback здесь позже используется:

ct_callback(context, NULL, CS_SET, CS_CLIENTMSG_CB,
                  (CS_VOID *)clientmsg_callback);

Я выхожу из примера программы отсюда:

http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc35570.1570/html/clcprgde/clcprgde10.htm

clientmsg_callback будет реализован позже. Я думаю, что образец изначально был написан с учетом C, а не C ++. Возможно, это как-то связано с этим?

Чарли Элверсон
источник
6
Есть ли поблизости директивы #if/ #ifdef/ #ifndef/, #elseгде вместо этого могло бы быть другое определение? Это может иметь значение при использовании в других макросах, особенно рядом с #или ##. Это могло быть просто для стиля комментирования. Недостаточно контекста, чтобы действительно ответить.
aschepler 03
В качестве общего ответа: потому что у кого-то может быть причина хотеть измениться PROTOTYPE. Если вы видите в коде странные определения, которые кажутся бесполезными, подумайте о потенциальной гибкости, если кто-то хочет что-то изменить для удобства.
Apollys поддерживает Монику

Ответы:

130

В былые времена действительно, очень раннего C, не существовало такой вещи, как прототип. Списки аргументов функции идут после скобок функции, например :

square(x)
int x;
{
int y = x * x;
return y;
}

В наши дни, конечно, аргументы заключаются в круглые скобки:

square(int x)
{
int y = x * x;
return y;
}

Обратите внимание на тип возвращаемого значения «отсутствует»; Функции C использовались для неявного возврата int, и только если вам нужен был другой тип возврата, вы должны были сказать, что это было.

У объявлений функций был еще один набор правил. Объявление функции в K&R C (древняя версия) не имело аргументов:

int square();

А прототипы функций в ANSI C имеют список аргументов:

int square(int x);

Во время перехода люди использовали дурацкие макросы, чтобы они могли компилировать обоими способами:

int square(PROTOTYPE(int x));

С участием

#define PROTOTYPE(s)

это расширится до первой версии.

С участием

#define PROTOTYPE(s) s

он расширится до второго.

Что касается «лишних» скобок в коде вопроса, они необходимы, когда в списке аргументов более одного аргумента. Без них вызов макроса имеет более одного аргумента, поэтому не будет соответствовать макросу, определенному только с одним аргументом:

PROTOTYPE(int x, int y)   // error: too many arguments
PROTOTYPE((int x, int y)) // ok: only one argument (enclosed in parentheses)
Пит Беккер
источник
10
Вау. Взрыв из прошлого. Одна из моих первых работ в программном обеспечении заключалась в удалении этого материала из существующей кодовой базы. Вызвал здоровое уважение к массиву однострочных программ, изменяющих текст в Unix.
user4581301 04
15
Я не понять , как работают эти определяет, как же #define PROTOTYPE(s)с входом int x;преобразуемый x? Мне кажется, что это завершается пустой строкой
Ferrybig
3
@Ferrybig - извини, я перепутал. Это прототип , который получает определенный таким образом. В K&R C у прототипа не было аргументов, а в ANSI C он имеет стиль списка аргументов, который мы привыкли видеть.
Пит Беккер
1
Эту практику также можно увидеть в zlib.hзаголовке библиотеки zlib с OF()макросом: github.com/madler/zlib/blob/master/zlib.h
Пол
@PaulBelanger Фактическое определение находится в zconf.h.
SS Anne
16

Подобные макросы будут использоваться в прототипах в файле заголовка, чтобы сделать что-то вроде этого:

int foo PROTOTYPE((int bar));

Если был обнаружен ANSI C (__STDC__ определен как 1), это расширилось бы до:

int foo(int bar);

Если ANSI C не был обнаружен, это расширилось бы до:

int foo();

что было распространено до стандартизации C.

Некоторые библиотеки до сих пор это делают; если вы заглянете внутрь tcpd.h(если он у вас есть), вы увидите:

/* someone else may have defined this */
#undef  __P

/* use prototypes if we have an ANSI C compiler or are using C++ */
#if defined(__STDC__) || defined(__cplusplus)
#define __P(args)       args
#else
#define __P(args)       ()
#endif

Это хорошо объясняет.

Что касается двойных скобок, это __P(arg1, arg2)приведет к синтаксической ошибке (передача макросу слишком большого количества аргументов), а__P((arg1, arg2)) будет нормально (только один заключен в круглые скобки).

Это похоже на __extension__((...))GNU C. В компиляторах, отличных от GNU, просто #define __extension__(unused)иметь полупереносимый код, поскольку дается только один «аргумент», заключенный в круглые скобки.

СС Энн
источник