基于两个可能的结构的未知空指针的访问类型标志?

时间:2018-11-14 16:32:20

标签: c void-pointers octree

我目前正在C语言中工作自己的八叉树。该树将包含数十亿个对象,因此内存效率是关键。为此,我目前使用一个带有标志和联合的结构,但我认为它不干净,并且浪费了内部节点的空间,因为我只需要一个8位标志,但为64位保留了内存。指数。我的代码当前如下:

typedef struct _OctreeNode
{
    uint64_t location_code;
    union
    {
        uint8_t child_exists;
        uint64_t object_index;
    } data;
    uint8_t type;
} OctreeNode;

我想将其分为两个不同的结构。一叶节点和一内节点。如下:

typedef struct _OctreeInnerNode
{
    uint64_t location_code;
    uint8_t child_exists;
    uint8_t type;
} OctreeInnerNode;

typedef struct _OctreeLeafNode
{
    uint64_t location_code;
    uint64_t object_index;
    uint8_t type;
} OctreeLeafNode;

现在,基于位置代码的哈希值,我的无序地图出现了问题。它使用空指针,因此存储两个不同的结构不是问题。我知道一种可能是将标志作为第一个元素,并取消对标志数据类型的指针的引用以得出类型,例如:

typedef struct _OctreeLeafNode
{
    uint8_t type;
    uint64_t location_code;
    uint64_t object_index;
} OctreeLeafNode;

void
func(void* node)
{
    uint8_t type = *(uint8_t*)node;
    if (type == LEAF_NODE) {
        OctreeLeafNode* leaf_node = (OctreeLeafNode*)node;
    }
}

我想知道是否有更清洁的方法。还是不建议这样做?我应该如何处理结构和void指针的多种可能性?

谢谢!

2 个答案:

答案 0 :(得分:5)

这是C中常用的一种方法。

但是只要将这些字段放在结构的开始(第一个字段),就不要更改其位置。另外,您需要将它们保留在所有结构中。

此方法的常见示例是结构中的version字段(在您的情况下为type)。您可以将它们保留在结构的开头,然后通过类似的方法检查结构的版本。像这样的东西:

struct _base {
    uint8_t ver;
};

#define TYPE_OLD 0
struct _a_old {
    struct _base info;
    uint8_t a;
};

#define TYPE_NEW 1
struct _a_new {
    struct _base info;
    uint8_t a;
    uint8_t b;
};

现在,您可以通过将数据投射到struct _base并检查ver字段来识别不同的类型。

unsigned char* buf = ...
switch (((struct _base*)buf)->ver)
{
    case TYPE_OLD:
    {
        struct _a_old* old = (struct _a_old*)buf;
        // ...
        break;
    }
    case TYPE_NEW:
    {
        struct _a_new* old = (struct _a_new*)buf;
        // ...
        break;
    }
    default:
        // ...
}

答案 1 :(得分:3)

这将起作用,假设type字段在每个结构中位于第一位。指向结构的指针可以安全地转换为指向其第一个成员的指针,因此假设您的结构看起来像这样:

typedef struct _OctreeInnerNode
{
    uint8_t type;    // type goes first
    uint8_t child_exists;   // put uint8_t members together to keep size down
    uint64_t location_code;
} OctreeInnerNode;

typedef struct _OctreeLeafNode
{
    uint8_t type;   // type goes first
    uint64_t object_index;
    uint64_t location_code;
} OctreeLeafNode;

您可以将OctreeInnerNode *OctreeLeafNode *强制转换为uint8_t *。然后这是可能的:

void func(void* node) {
    uint8_t type = *(uint8_t*)node;
    if (type == LEAF_NODE) {
        OctreeLeafNode *leafNode = node;
        ...
    } else if (type == INNER_NODE) {
        OctreeInnerNode *innerNode = node;
        ...
    }
}

...

OctreeLeafNode leaf = { LEAF_NODE, 2, 3 };
OctreeInnerNode inner = { INNER_NODE, 5, 1 };

func(&leaf);
func(&inner);

根据C standard的6.7.2.1p15节对此进行保证:

  

在结构对象中,非位字段成员和   位域所在的单元的地址会增加   声明它们的顺序。 指向结构的指针   经过适当转换的对象指向其初始成员(或   如果该成员是位域,则指向它所在的单元   居住),反之亦然。内部可能有未命名的填充   一个结构对象,但不是在其开头