Я пишу DEBUG_MSG
для печати отладочных сообщений
#define DEBUG_MSG(msg_str) _DEBUG_MSG_GENERIC(msg_str)
_DEBUG_MSG_GENERIC
потому что я хотел бы:
int
, когда входной параметр равен int
char*
, когда входной параметр равен char*
и его реализация:
#define _DEBUG_MSG_GENERIC(strs) \
_Generic( (strs), \
int: _DEBUG_MSG_INT, \
default: _DEBUG_MSG_STR \
)(strs)
Теперь я хотел бы реализовать _DEBUG_MSG_INT
и _DEBUG_MSG_STR
с функцией макроса и printf
:
#define _DEBUG_MSG_INT(val) printf("%d\n", val);
#define _DEBUG_MSG_STR(str) printf("%s\n", str);
Но я получил сообщение об ошибке:
main.c:14:30: error: ‘_DEBUG_MSG_INT’ undeclared (first use in this function); did you mean ‘DEBUG_MSG’?
14 | int: _DEBUG_MSG_INT, \
| ^~~~~~~~~~~~~~
Как мне это решить?
Поддерживает ли _generic
только функцию (указатель на функцию) и не поддерживает функцию макроса?
#include <stdio.h>
#define DEBUG_MSG(msg_str) _DEBUG_MSG_GENERIC(msg_str)
#define _DEBUG_MSG_GENERIC(strs) \
_Generic( (strs), \
int: _DEBUG_MSG_INT, \
default: _DEBUG_MSG_STR \
)(strs)
#define _DEBUG_MSG_INT(val) printf("%d\n", val)
#define _DEBUG_MSG_STR(str) printf("%s\n", str)
int main()
{
DEBUG_MSG("str");
DEBUG_MSG(5);
}
Я полагаю, что ваша проблема в том, что препроцессор делает только один проход исходного кода, поэтому printf
не заменяется.
Быстрым решением было бы определить _DEBUG_MSG_INT(val)
и _DEBUG_MSG_STR(str)
как реальные функции, например:
void _DEBUG_MSG_INT(int val) {
printf("%d\n", val);
}
void _DEBUG_MSG_STR(char * str) {
printf("%s\n", str);
}
Компилятор оптимизирует дополнительные накладные расходы на вызов функции и будет вести себя так, как если бы вы вызывали printf
напрямую.
Спасибо за ваш ответ. Собственно, я и сейчас использую эту версию, но заменяю void
на static inline void
. Это может устранить стоимость вызова функции. Но inline
, похоже, не может гарантировать, что RVO устранит стоимость вызова функции.
Использование inline
может зависеть от настроек языка/стандарта вашего компилятора. В строгом C89/C90/ANSI вообще не было ключевого слова inline
. В большинстве случаев это всего лишь подсказка для компилятора, и на самом деле он может не встраивать его. Обычно небольшие функции все равно встраиваются, если только вы не берете адрес функции или некоторые другие вещи.
@gengumby, использование static inline
позволяет использовать эти функции в заголовках. В противном случае компиляция нескольких единиц перевода, скорее всего, не удастся из-за нескольких определений этих функций.
@tstanisl Небольшая придирка: он позволяет нескольким единицам перевода видеть определение, а не только объявление, тем самым позволяя компилятору встраивать эту функцию во многих местах (по своему усмотрению). Он должен быть объявлен static
в заголовке, потому что нет гарантии, что он будет встроен в использование все, и поэтому должен иметь только одно определение/экземпляр во всех единицах перевода.
Проблема в том, что и _DEBUG_MSG_INT
, и _DEBUG_MSG_STR
являются функциональными макросами, поэтому они раскрываются только в том случае, если за ними следует ()
.
Обратите внимание, что раскрытие макроса происходит до фактической компиляции C, поэтому _Generic
является не чем иным, как общим идентификатором на этапе препроцессора.
Я предлагаю использовать _Generic
не для выбора указателя функции, а скорее для спецификатора форматирования, который будет использоваться в printf()
. Пытаться:
#define _DEBUG_MSG_GENERIC(arg) printf( _DEBUG_MSG_FMTSPEC(arg), arg)
#define _DEBUG_MSG_FMTSPEC(arg) \
_Generic( (arg), int: "%d\n", default: "%s\n")
О~ большое спасибо! Я не думал, что это может быть использовано для моего случая.
_Generic
не является операцией препроцессора и не может использоваться для выбора макрофункций препроцессора. Код после :
в его случаях должен быть выражением C (в частности, выражение-присваивания).
Код, который у вас есть в этих позициях, — это _DEBUG_MSG_INT
и _DEBUG_MSG_STR
. Это имена макросов препроцессора.
Эти макросы препроцессора являются макросами, подобными функциям. Они заменяются макросом только тогда, когда за ними следует (
. В вашем коде после них нет (
, поэтому они не заменяются.
Это означает, что код после повторной обработки выглядит как int : _DEBUG_MSG_INT,
. Поэтому компилятор пытается интерпретировать _DEBUG_MSG_INT
как выражение. Поскольку _DEBUG_MSG_INT
не является объявленным идентификатором, компилятор сообщает об ошибке, что он не объявлен.
Таким образом, ваш код _Generic( (strs), int: _DEBUG_MSG_INT, default: _DEBUG_MSG_STR )(strs)
пытается использовать выбор после предварительной обработки _Generic
, чтобы выбрать макрос времени предварительной обработки (либо _DEBUG_MSG_INT
, либо _DEBUG_MSG_STR
), а затем обработать этот макрос как функциональный макрос с (strs)
, который появляется после _Generic
. Это просто не может работать; после предварительной обработки _Generic
не может выбирать имена макросов предварительной обработки.
Вы могу используете функции макроса в универсальных операторах, но между идентификатором макроса и списком аргументов не должно быть ничего. Например, изменение
_DEBUG_MSG_INT,
на_DEBUG_MSG_INT(strs),
было бы распознается препроцессором как макрофункция. К сожалению, это приведет к появлению предупреждений по другим причинам (даже после того, как вы удалите(strs)
, который следует за оператором_Generic
.