【问题标题】:Elegant way of accessing union members of a structure访问结构的联合成员的优雅方式
【发布时间】:2023-04-11 03:00:01
【问题描述】:

有没有办法反转#define指令?

在下面的例子中

#define ZERO 0
#define ONE 1
#define TWO 2
#define THREE 3

是否可以从整数值 2 中检索两个?

此示例来自 C 代码,但如果需要,我可以使用一些 C++ 代码。我的目标是能够分解这种形式的一些虚假的 switch-case 循环:

switch(num)
{
   case ZERO:
      return std::to_string(foo.V_ZERO);
   case ONE:
      return std::to_string(foo.V_ONE);
   case TWO:
      return std::to_string(foo.V_TWO);
   case THREE:
      return std::to_string(foo.V_THREE);
}

其中 foo 是这样的结构的一个实例:

struct Foo
{
   union Val
   { 
      int V_ZERO;
      int V_ONE;
      double V_TWO; // nonsense: just to say that types are not the same
      int V_THREE;
   };
};

我的限制如下:

  1. 我无法删除#define 提供的功能,即我可以写一些等效的东西,例如一个枚举,但我不能丢失 ZERO 和 0、ONE 和 1 等之间的映射;
  2. 现有代码是用 C 编写的,我无法用 C++ 重写它。不过,我可以编写一些补充的 C++ 代码。

我有一些简化代码的想法,但我想知道是否有一种众所周知的优雅方法可以做到这一点,尤其是通过一些模板或预处理器指令。

编辑:添加 std::to_string 的用法表示我对知道如何转换或处理联合中的多种类型不感兴趣。

【问题讨论】:

  • 请在编程中定义优雅的方式。当一个人使用PUSH ADDR; RET 只是为了进行间接的子程序调用时,它应该被称为优雅吗??

标签: c++ c c-preprocessor unions


【解决方案1】:

如果您想自动化某些过程,一种选择是使用 xmacros。它们很老套,但根据您的代码大小,可能会使维护(例如添加新条目)更容易,但代价是您同事的愤怒:

定义xmacro列表:

#define XLIST(xmacro) \
   xmacro(ZERO, 0) \
   xmacro(ONE, 1) \
   xmacro(TWO, 2) \
   xmacro(THREE, 3) \

然后在您想要遍历所有项目时使用它:

// create an enum containing all items
#define xmacro(name, value) name,
enum Item
{
    XLIST(xmacro)
};
#undef xmacro

// get the number of items
#define xmacro(name, value) +1
const int NumberOfItems = 0 XLIST(xmacro);
#undef xmacro

// enum -> value
int itemToValue(enum Item item)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return value;
        XLIST(xmacro)
#undef xmacro
    }

    return -1;
}

// get enum name
const char * getItemName(enum Item item)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return #name;
        XLIST(xmacro)
#undef xmacro
    }

    return NULL;
}

这将被预处理为:

enum Item
{
    ZERO,
    ONE, 
    TWO, 
    THREE,
};

const int NumberOfItems = 0 +1 +1 +1 +1; // == 4

int itemToValue(enum Item item)
{
    switch (item)
    {
        case ZERO: return 0; 
        case ONE: return 1; 
        case TWO: return 2; 
        case THREE: return 3;   
    }

    return -1;
}

const char * getItemName(enum Item item)
{
    switch (item)
    {
        case ZERO: return "ZERO"; 
        case ONE: return "ONE"; 
        case TWO: return "TWO"; 
        case THREE: return "THREE";

    }

    return NULL;
}

您可以从中创建几乎任何您想要的映射,即对于您的结构,您可以使用类似于 @Jean-François 所写的内容:

// get struct value by item type
double getValueByName(enum Item item, struct Foo values)
{
    switch (item)
    {
        // create a mapping from enum to x
#define xmacro(name, value) case name: return values.V_##name;
        XLIST(xmacro)
#undef xmacro
    }

    return -1;
}

【讨论】:

  • 非常感谢 Groo。我听说过 X 宏,但我从未使用过它们。让我先尝试使用它们,然后我会回复你。
  • @Aleph:x-macros 的语法可能会让毫无戒心的开发人员感到困惑,因此请确保不要将它们变成习惯。 :) 另请注意,您可以在 x-macro 列表中提供任何其他元数据,即xmacro(ZERO, 0, int)、xmacro(TWO, 2, double),大多数列表调用不会使用所有参数,但有些可能会使用,即 (xmacro(name, value, type) "type of " #name " is " #type)。
  • 除了项目数的计算外,一切都编译。我收到以下错误消息:“test.cpp:35:25: error: expected initializer before numeric constant const int NumberOfItems 0 XLIST(xmacro);”
  • 一定是错字,但我不知道预处理器指令足以修复它。
  • @Aleph:对不起,这是我的错字,应该是const int NumberOfItems = 0+1+1+1...;(它缺少赋值运算符=)这是使用宏的缺点之一,通常是这样的小错字很难被发现。但是,大多数编译器允许转储预处理器输出,即编译器在完成所有宏扩展和替换后如何查看您的代码,因此这通常可以帮助您识别问题。
【解决方案2】:

不,这是不可能的。

预处理器#defines 被预处理阶段的文本替换为它们的定义。实际的编译器永远不会看到这些符号。你觉得TWO 是,在运行时是什么?它已经被整数文字 2 替换,所以没有任何变化。

const int two_define = TWO;
const int two_literal = 2;

都会将整数值2 放入各自的变量中,没有什么神奇的气味可以以某种方式将TWO 与2 区分开来。编译器将看到const int two_define = 2;,因为预处理器符号将消失。

另外:假设您同时返回ints 和doubles,函数的实际返回类型是什么?这意味着它是double。

【讨论】:

  • 你是对的放松。我实际上试图找到一些替代解决方案。不用担心返回类型。假设所有类型都在函数的最后转换为字符串,以简化问题。但是,union 的类型可能非常不同(int、double、a​​rray、char*、struct、...)
【解决方案3】:

在一般情况下,您必须求助于“X 宏”之类的东西,正如另一个答案中所建议的那样。但这是其他一切都失败时的最后手段。如果数字完全是任意的,你就必须去那里。

但是,在这种特定情况下,数字是相邻的并且从零开始。这需要enum 与查找表相结合。实现它的标准方法是这样的:

#include <stdio.h>

typedef enum
{
  ZERO,
  ONE,
  TWO,
  THREE,
  SUPPORTED_NUMBERS
} number_t;

const char* STR_NUMBER [] =
{
  "ZERO",
  "ONE",
  "TWO",
  "THREE",
};

_Static_assert((sizeof STR_NUMBER / sizeof *STR_NUMBER) == SUPPORTED_NUMBERS,
               "Error: enum does not correspond to look-up table.");

int main (void)
{
  for(number_t i=0; i<SUPPORTED_NUMBERS; i++)
  {
    printf("%d %s\n", i, STR_NUMBER[i]);
  }
}

【讨论】:

  • +1;不过我还是会用 xmacros 创建查找表,这样维护起来更容易。此外,我会使用指定的初始化程序(除非您不能实现 C++ 兼容性......)。这甚至可以处理枚举中的间隙和别名(保留最后一个别名作为最终名称)——是的,我知道,这不适合 every 枚举...
  • 谢谢伦丁。你能解释一下静态断言,因为我不明白吗?
  • @Aconcagua 关于指定的初始化器和枚举,你可能会觉得这很有趣:stackoverflow.com/questions/43043246/…
  • @Aleph 验证枚举项的数量是否对应于数组项的数量。枚举中的最后一项 SUPPORTED_NUMBERS 仅作为计数器存在。在我的示例中,它将是 4。静态断言检查创建的数组是否还包含 4 个项目。或者,您可以使用const char* STR_NUMBER [SUPPORTED_NUMBERS] = ...,但这并不能防止数组中的项目太少,所以这是一个更糟糕的解决方案。
  • @Aleph “x 宏”的缩写,另一个查找表。 const size_t V_ITEM[] = { offsetof(union Val, V_ZERO), offsetof( ...。然后将uint8_t 指针指向(uint8_t*)&amp;the_struct + V_ITEM[i]。虽然相当丑陋,但首先使用“变体”也是如此。我怀疑问题的根源在于底层程序设计,对于您要解决的任何问题,肯定有比使用变体更好的解决方案。
【解决方案4】:

对于这两个问题:

有没有办法反转#define指令?

是否可以从整数值 2 中检索两个?

也许映射结构数组适合你:

#define ZERO 0
#define ONE 1
#define TWO 2
#define THREE 3

typedef struct
{
    int number;
    const char *name;
} name_map_t;


#define MAP_NAME_STR(id) { id, #id },


static const name_map_t name_map_table[] =
{
    MAP_NAME_STR( ZERO  )
    MAP_NAME_STR( ONE   )
    MAP_NAME_STR( TWO   )
    MAP_NAME_STR( THREE )
};

然后只需循环 name_map_table 数组即可找到想要的对应对象。

【讨论】:

    【解决方案5】:

    我发现了你的问题,当我遇到这种枚举模式时,我会告诉你我该怎么做:

    假设我有一个值枚举,我想将其打印到switch,以表示数组索引等。

    首先我编写了一个包含文件,它将支持我将要对这个枚举类型进行的各种使用:

    enum(INITIAL_STATE, 0, "This is the initial state")
    enum(FLAG_READ, 1, "We have read the flag symbol")
    ...
    

    然后我使用不同的宏定义来扩展这个寄存器数据集,如下:

    struct enum MyEnum {
    #define enum(val,ix, string) val,
    #include "myenumdef.i"
    #undef enum
    };
    
    /* then, later in the same file ... */
    
    char *MyEnumStrings[] = {
    #define enum(val,ix, string) string,
    #include "myenumdef.i"
    #undef enum
    };
    
    /* .... */
    
    char *MyEnumNames[] = {
    #define enum(val, ix, string) #val, /* the string equivalent of enum names */
    #include "myenumdef.i"
    #undef enum
    };
    
    /* and more complex forms... like */
    
    struct myEnumDesc {
        int   e_val;
        int   e_ix;
        char *e_name;
        char *e_desc;
    } enum_table[] = {
    #define enum(val,ix,string) val, ix, #val, string,
    #include "myenumdef.i"
    #undef enum
    }; /* enum_table */
    
    
    /* ... even, when I want to switch on them */
        switch(val) {
    #define enum(val,ix,string) case VAL_##val: return string"("#val"="#ix")";
    #include "myenumdef.i"
    #undef enum
        default: return "invalid val";
        } /* switch */
    

    只需用上面的方法准备一个示例,然后通过 C 预处理器运行它,看看最终的 C 代码是什么样子的。

    您可以根据需要多次执行此操作(即使在单个文件中),我只需要更改宏 enum 的方式(在这种情况下,您甚至可以定义多个宏名称并将它们用于在包含数据文件之前构建更复杂的数据依赖关系。如果您想向枚举添加一个常量,您只需将其添加到.i 文件中,如上所示编码,一切都会适应变化。

    您要求仅使用 C 的解决方案,因此我给出了我所知道的更近似的方式来处理同一组项目的不同定义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-28
      • 2014-11-30
      • 1970-01-01
      相关资源
      最近更新 更多