Подтвердить что ты не робот

Извлечь имя функции внутри макроса

В C нам часто приходится запускать такой код

if (! somefun(x, y, z)) {
    perror("somefun")
}

Можно ли создать макрос, который используется следующим образом:

#define chkerr ...
chkerr(somefun(x, y, z));

будет скомпилирован с указанным выше?

Я уже знаю, что могу использовать макрос __VA_ARGS__, но это потребовало бы, чтобы я назвал его как

chkerr(somefun, x, y, z)         
4b9b3361

Ответ 1

Короткий вариант (вы уже заметили):

#define chkErr(FUNCTION, ...)  \
    if(!FUNCTION(__VA_ARGS__)) \
    {                          \
        perror(#FUNCTION);     \
    }

Помните, что это может вызвать большие проблемы в вложенных if/else или подобных конструкциях:

if(x)
    chkErr(f, 10, 12) //;
                      //^ semicolon forgotten!
else
    chkErr(f, 12, 10);

будет компилировать код, эквивалентный следующему:

if(x)
{
    if(!f(10, 12))
        perror("f");
    else if(!f, 12, 10))
        perror("f");
}

Совершенно очевидно не то, что было предназначено для if/else, написанного с помощью макросов... Поэтому вы действительно должны предпочесть, чтобы он выглядел как настоящая функция (требующая точку с запятой):

#define chkErr(FUNCTION, ...)      \
    do                             \
    {                              \
        if(!FUNCTION(__VA_ARGS__)) \
        {                          \
            perror(#FUNCTION);     \
        }                          \
    }                              \
    while(0)

Вы бы назвали это следующим образом:

chkErr(someFunction, 10, 12);

В случае ошибки вывод будет:

someFunction: <error text>

Однако, это скрывает тот факт, что функция фактически вызвана, что затрудняет понимание для "аутсайдеров". Тот же вывод, не скрывающий вызов функции, но требующий дополнительной запятой между функцией и аргументами (по сравнению с обычным вызовом функции):

#define chkErr(FUNCTION, ARGUMENTS) \
do                                  \
{                                   \
    if(!FUNCTION ARGUMENTS)         \
    {                               \
        perror(#FUNCTION);          \
    }                               \
}                                   \
while(0)

chkErr(someFunction,(12, 10));
//                 ^ (!)

Другой вариант с очарованием сохранения вызова функции распечатает весь этот вызов функции:

#define chkErr(FUNCTION_CALL)   \
do                              \
{                               \
    if(!FUNCTION_CALL)          \
    {                           \
        perror(#FUNCTION_CALL); \
    }                           \
}                               \
while(0)

chkErr(someFunction(10, 12));

В случае ошибки вывод будет:

someFunction(10, 12): <error text>

Добавление: Если вы действительно хотите точно выводить результат, как показано в вопросе, и все еще сохраняете вызов функции (без запятой между ними), вы немного в беде. На самом деле это возможно, но для этого требуется дополнительная работа:

Проблема в том, как препроцессор работает с аргументами макроса: каждый аргумент является токеном. Он может легко комбинировать токены, но не может их разделить.

Оставляя какие-либо запятые, получается, что макрос принимает один единственный токен, как и в моем втором варианте. Конечно, вы можете это сделать, как и я, но вы получите аргументы функции. Это строковый литерал, и поскольку предварительный процессор не может изменять строковые литералы, вы должны работать с ними во время выполнения.

Затем следующая проблема заключается в том, что строковые литералы не поддаются изменению. Поэтому вам нужно изменить копию!

Следующий вариант сделает все это для вас:

#define chkErr(FUNCTION_CALL)                                 \
do                                                            \
{                                                             \
    if(!FUNCTION_CALL)                                        \
    {                                                         \
        char function_name[] = #FUNCTION_CALL;                \
        char* function_name_end = strchr(function_name, '('); \
        if(function_name_end)                                 \
            *function_name_end = 0;                           \
        perror(function_name);                                \
    }                                                         \
}                                                             \
while(0)

Хорошо, решите, если это стоит усилий...

Кстати, пробелы между именем функции и открывающей скобкой не устраняются. Если вы хотите быть совершенным:

unsigned char* end = (unsigned char*) function_name;
while(*end && *end != '(' && !isspace(*end))
    ++end;
*end = 0;

Или, гораздо приятнее (спасибо chqrlie за подсказку):

function_name[strcspn(function_name, "( \t")] = 0;

Все, что я могу придумать, потребует дополнительного этапа предварительной обработки:

#define CAT(X, Y) CAT_(X, Y)
#define CAT_(X, Y) X ## Y

#define chkErr(FUNCTION_CALL)                 \
do                                            \
{                                             \
    if(!FUNCTION_CALL)                        \
    {                                         \
        perror(CAT(CHK_ERR_TEXT_, __LINE__)); \
    }                                         \
}                                             \
while 0

chkErr(function(10, 12);

А, да, это приведет к следующему коду:

if(!function(10, 12))
{
    perror(CHK_ERR_TEXT_42);
}

А теперь, откуда взять эти макросы? Ну, предварительная обработка, помните? Возможно, perl или python script, e. г. создавая дополнительный файл заголовка, который вы должны включить. Вам нужно будет убедиться, что эта предварительная обработка выполняется каждый раз до того, как выполняется предварительный процессор компилятора.

Ну, все не невозможно решить, но я оставлю это мазохистам среди нас...

Ответ 2

C11 6.4.2.2 Предопределенные идентификаторы

Идентификатор __func__ должен быть неявно объявлен переводчиком так, как если бы сразу после открытия скобки каждого определения функции было объявлено

static const char __func__[] = "function-name";
Появился

где function-name - имя лексически-охватывающей функции.

Вы можете использовать его следующим образом:

#define chkErr(exp)  do { if (!(exp)) perror(__func__); } while (0)

chkerr(somefun(x, y, z));

К сожалению, это приведет к появлению сообщения об ошибке с именем вызывающей функции, а не somefun. Вот простой вариант, который должен работать и даже создавать более информативные сообщения об ошибках:

#define chkErr(exp)  do { if (!(exp)) perror(#exp); } while (0)

chkerr(somefun(x, y, z));

В случае, если somefun(x, y, z) возвращает ненулевое значение, сообщение об ошибке будет содержать строку "somefun(x, y, z)".

Вы можете комбинировать оба метода, чтобы дать как нарушающий вызов, так и местоположение:

#include <errno.h>
#include <stdio.h>
#include <string.h>

#define chkErr(exp)  \
    do { if (!(exp)) \
        fprintf(stderr, "%s:%d: in function %s, %s failed: %s\n",\
                __FILE__, __LINE__, __func__, #exp, strerror(errno)); \
    } while (0)

chkerr(somefun(x, y, z));

Это предполагает, что somefun() возвращает 0 или NULL в случае ошибки и устанавливает errno соответственно. Обратите внимание, однако, что большинство системных вызовов возвращают не равные нулю в случае ошибки.

Ответ 3

Да, это возможно с уродливым, небезопасным переменным макросом:

#define chkerr(func, ...) \
if(!func(__VA_ARGS__))    \
{                         \
  perror(#func);          \
}

...
chkerr(somefunc, 1, 2, 3);

Но это очень плохая идея.


Призыв к здравомыслию:

Если бы был только исходный код с простым выражением if, читатель подумал бы: "Здесь они называют функцию и выполняют некоторые основные функции контроля ошибок. Хорошо, основные вещи. Перемещение...". Но после изменений любой, кто читает код, вместо этого замораживает и думает: "WTF - это???".

Вы никогда не сможете написать макрос, который более ясный, чем оператор if, что делает оператор if выше макроса.

Некоторые правила:

  • Функциональные макросы опасны и нечитабельны. Их следует использовать только в качестве крайней меры.
  • Избегайте изобретать свой собственный секретный макроязык с помощью макросов типа функции. Программисты C, которые читают ваш код, знают C. Они не знают ваш секретный макроязык.
  • "Во избежание ввода" часто является плохим объяснением для решений по разработке программ. Избежание повторения кода - это хорошее обоснование, но переход до крайности повлияет на читаемость кода. Если вы избежите повторения кода и сделаете код более читаемым в одно и то же время, это хорошо. Если вы это сделаете, но код станет менее читаемым, его трудно оправдать.

Ответ 4

Вы можете использовать формат оригинального вызова:

chkerr(somefun(x, y, z));

С макросом и вспомогательной функцией:

#define chkerr(fcall) \
    if (!fcall) { \
        perror(extract_fname(#fcall)); \
    }
const char *extract_fname(const char *fcall);

Функция extract_fname получит текст и вернет все до открытой круглой скобки.

Ответ 5

Невозможно извлечь только имя функции. Процессор C видит литералы, которые вы передаете как одиночные токены, которые нельзя манипулировать. Ваши единственные варианты - напечатать функцию с такими аргументами, как Aconcague, или передать имя как отдельный параметр:

#define chkErr(FUNCTION_NAME, FUNCTION_CALL) \
if(!FUNCTION_CALL) \
{ \
    perror(#FUNCTION_NAME); \
}

chkErr(someFunction, someFunction(10, 12));