【问题标题】:C dynamic macro choosingC 动态宏选择
【发布时间】:2021-03-11 00:39:12
【问题描述】:

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

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

标题应该是这样的:

# 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"
...

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

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

【问题讨论】:

  • I'm trying to call a macro by building it's name during runtime. 宏在运行时不存在。
  • 你不能。预处理器不知道e 的值是什么。那样不行。
  • Sooo 为什么不只是一个数组? I could have an array with the msgs and pass it to the function 是的,有一个带有msgs 在函数中的数组并使用它。但是I have a lot of constraint - 这些约束的原因是什么?
  • @KamilCuk 这是一个赋值,约束来自评估它的实体。我对数组方法的问题是拥有 10 个不同的 msg 就可以了,但是一旦我得到几百个,我就需要多个文件来声明它。 (我每个文件只能有 5 个函数,每个函数 25 行)。我不同意很多限制,他们很烂:(
  • “我有很多限制:……等等。” 不知道确切的限制就很难工作。

标签: c c-preprocessor


【解决方案1】:

宏是在编译前扩展的,所以编译本身没有宏扩展。通过控制宏定义中的所有重复序列并允许您在只有一个地方。但是一旦宏扩展代码进入编译器,就没有宏了。

【讨论】:

    【解决方案2】:

    命题 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)
    

    注意:这部分回答了我使用多行宏的问题...

    命题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
    

    【讨论】:

    • 如果我没有那么多限制,这正是我所需要的。不幸的是,由于这些不合逻辑的限制,我无法使用它。我会为将来保存它,因为这似乎很有用。非常感谢。
    • ## 运算符的第二个命题呢?
    • 这将违背“无参数化宏”。我要做的只是添加一个字符串参数并在调用 print_error 时使用适当的宏。不完全是我想要的,但它有效......非常感谢您的帮助。
    【解决方案3】:

    正如我在 cmets 中所说: 将数组卸载到预处理器,这很像写出数组,但函数中的行数更少。

    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 中,这样您就可以确定错误的数量并确保不会调用错误的错误,这样做时我通常会编写一个将错误代码转换为的完整函数字符串,但正如你所说,这可能太长了。

    【讨论】:

    • 除了两个问题外,这将起作用。首先,我不能有多行宏,但这可以通过像我在回复您的第一个 msg 时所说的那样相互调用宏来解决。第二个问题是所有错误代码都必须是连续的,如果我想组织它们,我将无法跳到 100、200 等。
    • “顺序”和“跳过”是什么意思,能详细说明一下吗?
    • 多行宏不是问题。将宏的扩展拆分为多行严格用于人类可读性。如果满足要求,您可以将所有行合并为一条。
    • 假设我有 99 个错误,我需要在 1-99 范围内有错误代码,但如果我希望所有与文件相关的错误都在 1-99 范围内,所有图形错误都是 100-199,所有 libraryX 错误为 200-299 等我无法做到,或者我必须用空字符串/NULL 填充缺失的插槽
    • 是的,这听起来很不切实际,过度设计它只是为了最终出现 10 个错误,这听起来像是在浪费你的时间。
    【解决方案4】:

    你可以:

    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));
    

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

    【讨论】:

    • 不幸的是,由于限制,我无法使用此方法。不过感谢您的建议。
    猜你喜欢
    • 2012-12-10
    • 2018-09-26
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 2016-03-14
    • 1970-01-01
    • 2013-05-07
    • 2017-10-06
    相关资源
    最近更新 更多