C动态宏选择

C dynamic macro choosing

我想通过在运行时构建名称来调用宏。我想要一个遵循相同名称规则 ERROR_MSG_X 的宏列表,其中 X 是错误代码并根据变量调用它们。我有以下功能:

void    print_error(int e)
{
    printf("[ERROR] : code (%d)\n\t%s", e, ERROR_MSG_#e); //not proper syntax
}

header 类似于:

# define ERROR_MSG_1 "Failed to open file.\n"
# define ERROR_MSG_2 "Failed to read file.\n"
# define ERROR_MSG_3 "Failed to execute abc.\n"
...

我试着弄乱 ### 但没有得到我想要的结果。

我对我能做什么和不能做什么有很多限制,例如没有外部函数、没有多行宏、没有参数化宏、没有全局变量等。有没有办法计算带有传递值的宏,或者您对如何执行此操作有其他建议吗?我可以有一个带有消息的数组并将其传递给函数,但我真的不想那样做,而且我必须按顺序排列错误代码或浪费内存来存储它们。

正如我在评论中所说: 您将加载数组的任务卸载到预处理器,这非常类似于写出数组,但它在函数中占用的行数更少。

void print_error(int e){
    const char *errors[] = {ERRORS};
    printf("[ERROR] : code (%d)\n\t%s", e, errors[e]); 
}

对于 ERRORS 宏:

# define ERRORS "Failed to open file.\n",\
                "Failed to read file.\n",\
                "Failed to execute abc.\n"

编辑: 我还建议将错误代码放在 enum 中,这样您就可以确定错误的数量并确保不会调用错误的错误,这样做时我通常会编写一个完整的函数来转换错误编码成字符串,但正如你所说,这可能太长了。

你可以:

void print_error(int e) {
    const char *p = "";
    if (e == 1) p = ERROR_MSG_1;
    else if (e == 2) p == ERROR_MSG_2;
    else if (e == 3) p == ERROR_MSG_3;
    printf("[ERROR] : code (%d)\n\t%s", e, p);
}

或同switch case:。我会这样做:

void print_error(int e) {
    static const char *const strs[] = { ERROR_MSG_1, ERROR_MSG_2, ERROR_MSG_3 };
    const char *p = (1 <= e && (e - 1) < sizeof(strs)/sizeof(*strs)) ? strs[e - 1] : "";
    printf("[ERROR] : code (%d)\n\t%s", e, p);
}

你可以写一个宏:

#define GET_ERROR_MSG(e)  ( \
((e) == 1) ? ERROR_MSG_1 :
((e) == 2) ? ERROR_MSG_2 :
((e) == 3) ? ERROR_MSG_3 :
"")
printf("[ERROR] : code (%d)\n\t%s", e, GET_ERROR_MSG(e));

一个更混乱的宏然后它是值得的:

#define CASE_ERROR_MSG_(e, x)  ((e) == (x)) ? ERROR_MSG_##x :
#define GET_ERROR_MSG(e)  ( \
   CASE_ERROR_MSG_(e, 1) \
   CASE_ERROR_MSG_(e, 2) \
   CASE_ERROR_MSG_(e, 3)\
   "" )
printf("[ERROR] : code (%d)\n\t%s", e, GET_ERROR_MSG(e));

无论哪种方式,您必须以某种方式在一个地方枚举所有消息,以便运行时部分找到它。

宏在编译前展开,所以编译本身没有宏展开。它们在那里是为了节省您的输入,使您的代码更具可读性(有时)并避免容易出错的 C 构造更不容易出错(有时也是),方法是控制宏定义内的所有重复序列并允许您在只有一个地方。但是一旦宏扩展代码进入编译器,就没有宏了。

命题 1:X 宏

可以使用在Linux内核源代码中广泛使用的X macros概念。这个想法是提供一个名为 X:

的宏的多个扩展
#include <stdio.h>

// The X-macro
#define LIST_OF_ERRORS                             \
       X(ERROR_MSG_1, "Failed to open file.\n")    \
       X(ERROR_MSG_2, "Failed to read file.\n")    \
       X(ERROR_MSG_3, "Failed to execute abc.\n")

// 1st definition of X to define enum error codes
#define X(e, m) e,
enum {
LIST_OF_ERRORS
};
#undef X

// 2nd definition of X to define cases in a switch
void print_error(int e)
{
#define X(e, m) case e: printf("[ERROR] : code (%d)\n\t%s", e, m); break;
  switch(e) {
    LIST_OF_ERRORS
    default: printf("[ERROR] : unknown code (%d)\n", e);
  }
#undef X
}

int main(void)
{
  print_error(ERROR_MSG_2);
  print_error(ERROR_MSG_1);
  print_error(ERROR_MSG_3);
  print_error(10);

  return 0;
}

测试:

$ gcc err.c
$ ./a.out 
[ERROR] : code (1)
    Failed to read file.
[ERROR] : code (0)
    Failed to open file.
[ERROR] : code (2)
    Failed to execute abc.
[ERROR] : unknown code (10)

N.B.:这部分回答了我使用多行宏时的问题...

命题 2:## 运算符

在此命题中,您需要使用“硬编码”常量错误编号作为参数传递给 PRINT_ERROR() 宏,否则编译器会报错。如果您传递未知错误号,编译器也会抱怨。

#include <stdio.h>

#define ERROR_LABEL_0  "Error message#0\n"
#define ERROR_LABEL_1  "Error message#1\n"
#define ERROR_LABEL_2  "Error message#2\n"
#define ERROR_LABEL_3  "Error message#3\n"


#define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)


int main(void)
{
  PRINT_ERROR(2);
  PRINT_ERROR(1);
  PRINT_ERROR(3);

  return 0;
}

执行:

$ gcc macro.c
$ ./a.out
[ERROR] : code (2)
    Error message#2
[ERROR] : code (1)
    Error message#1
[ERROR] : code (3)
    Error message#3

但是如果你使用一个超出范围的变量或常量,编译器会报错:

[...]
int main(void)
{
  int err = 2;

  PRINT_ERROR(2);
  PRINT_ERROR(1);
  PRINT_ERROR(3);
  PRINT_ERROR(err); // <---- Compilation error
  PRINT_ERROR(10);  // <---- Compilation error

  return 0;
}

编译器显示的错误:

$ gcc macro.c
macro.c: In function 'main':
macro.c:15:72: error: 'ERROR_LABEL_err' undeclared (first use in this function); did you mean 'ERROR_LABEL_1'?
   15 | #define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)
      |                                                                        ^~~~~~~~~~~~
macro.c:25:3: note: in expansion of macro 'PRINT_ERROR'
   25 |   PRINT_ERROR(err); // <---- Compilation error
      |   ^~~~~~~~~~~
macro.c:15:72: note: each undeclared identifier is reported only once for each function it appears in
   15 | #define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)
      |                                                                        ^~~~~~~~~~~~
macro.c:25:3: note: in expansion of macro 'PRINT_ERROR'
   25 |   PRINT_ERROR(err); // <---- Compilation error
      |   ^~~~~~~~~~~
macro.c:15:72: error: 'ERROR_LABEL_10' undeclared (first use in this function); did you mean 'ERROR_LABEL_1'?
   15 | #define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)
      |                                                                        ^~~~~~~~~~~~
macro.c:26:3: note: in expansion of macro 'PRINT_ERROR'
   26 |   PRINT_ERROR(10);  // <---- Compilation error