C _Generic и макросы

Я пишу 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);
}

Вы могу используете функции макроса в универсальных операторах, но между идентификатором макроса и списком аргументов не должно быть ничего. Например, изменение _DEBUG_MSG_INT, на _DEBUG_MSG_INT(strs),было бы распознается препроцессором как макрофункция. К сожалению, это приведет к появлению предупреждений по другим причинам (даже после того, как вы удалите (strs), который следует за оператором _Generic.

user3386109 04.05.2022 08:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
53
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я полагаю, что ваша проблема в том, что препроцессор делает только один проход исходного кода, поэтому 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 устранит стоимость вызова функции.

curlywei 04.05.2022 07:56

Использование inline может зависеть от настроек языка/стандарта вашего компилятора. В строгом C89/C90/ANSI вообще не было ключевого слова inline. В большинстве случаев это всего лишь подсказка для компилятора, и на самом деле он может не встраивать его. Обычно небольшие функции все равно встраиваются, если только вы не берете адрес функции или некоторые другие вещи.

gengumby 04.05.2022 08:26

@gengumby, использование static inline позволяет использовать эти функции в заголовках. В противном случае компиляция нескольких единиц перевода, скорее всего, не удастся из-за нескольких определений этих функций.

tstanisl 04.05.2022 12:40

@tstanisl Небольшая придирка: он позволяет нескольким единицам перевода видеть определение, а не только объявление, тем самым позволяя компилятору встраивать эту функцию во многих местах (по своему усмотрению). Он должен быть объявлен static в заголовке, потому что нет гарантии, что он будет встроен в использование все, и поэтому должен иметь только одно определение/экземпляр во всех единицах перевода.

gengumby 05.05.2022 02:37
Ответ принят как подходящий

Проблема в том, что и _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")

О~ большое спасибо! Я не думал, что это может быть использовано для моего случая.

curlywei 05.05.2022 06:58

_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 не может выбирать имена макросов предварительной обработки.

Другие вопросы по теме