【问题标题】:Can a data structure contain multiple type of elements?一个数据结构可以包含多种类型的元素吗?
【发布时间】:2016-12-14 09:07:46
【问题描述】:

This program 是与旧单链表不同的方法。我没有创建单个结构并保持下一个相同类型节点的指针作为结构的成员,而是使用了三个不同的结构,每个结构中都有一个普通的long nextaddress 成员来指向下一个节点的地址,因为指针也一样。

每个节点还有一个int flag 成员作为结构的第一项,而data 部分由于其可变长度而位于结构的末尾。

这三个结构是内置类型longdoublechar的基本扩展。在访问结构时,我首先将节点的地址转换为int *,这样我就可以访问flag,而无需将地址完全转换为三个确定的结构。

然后分析flag,各种操作就搞定了。

所以这是我的问题。它可以称为有效链表吗?而且,它甚至是一个有效的数据结构吗?

【问题讨论】:

  • 包含多个事物的链表节点完全没有问题。其实这是完全正常的。
  • 我想如果那是你想要的就可以了。但是,为什么不让生活更轻松并拥有指向下一个节点的指针呢?如果您能够毫无问题地存储和访问数据,那么它就是一种有效的数据结构,只是不是很常见。
  • 如果我保留一个指向下一个节点的指针,那么我将不得不保留指向每个结构的其他结构的所有指针,甚至还有另一个标志来确定哪个正在使用。这就是我使用这种策略的原因。 @RoadRunner
  • @TimBiegeleisen,感谢您的回答。你能举一个使用这种结构的实际例子吗?
  • 当然,在我的脑海中,你可能有一个代表日志事件/对象的一些 C++ 类的列表。此类可以包含日志发生日期、日志内容和日志级别(例如调试、错误等)的成员。

标签: c data-structures linked-list singly-linked-list


【解决方案1】:

只要你有办法知道每个节点包含哪种类型,就可以这样做,比如flag 变量。

无论您使用哪种结构类型,您都可以假设flagnextaddress 将位于相同的结构偏移上,这似乎是合理的。虽然严格来说 C 语言不能保证这一点,但我相信它可以在任何系统上运行。

但是,您不能假设data 位于(uint8_t*)&my_struct + sizeof(int) + sizeof(long)。由于不同的对齐要求,此偏移量可能因结构而异。

更严重的问题是指针别名。您不能将指针 struct* x 转换为另一种指针类型 struct* y。它将编译,但这违反了 C 中的类型规则并调用未定义的行为(除非两个结构具有完全相同的成员)。使用积极优化的符合标准的编译器(如 GCC)不会按预期编译此类代码。 (What is the strict aliasing rule?)

为了安全起见并获得更好的整体程序设计,我建议您改为这样做:

typedef struct node
{
  long   nextaddress;
  type_t type; // some enum
  void*  data;
} node_t;

data 与节点分开分配。你会得到一个链式链表。

【讨论】:

  • 我没有将struct* y 转换为struct* x,反之亦然。我只将地址转换为gen_type,根据 C 标准,这是完全允许的,并且所有四个结构都将遵循初始公共成员的相同内存布局。开头没有未命名的填充,也没有在int flaglong nextaddress 之间的任何地方,因为两者都是4 的整数倍,再次引用你的话,will work on practice in every system out there。问题出在data 部分,更具体地说是在node_type_char
  • 这也可以通过使用一些计算来克服。
    offset_of_data = sizeof(node) + start_address - sizeof(data)
    需要明确的是,我希望在同一个列表中具有不同的结构,如果可以的话,我需要一个异构链表。
  • 同时,整个代码在 GCC 本身中进行测试和编译。所以我不太确定积极的优化。
  • @Subhranil 不,根据标准 C,它们将具有相同的内存布局对于第一个成员。在那之后,没有任何保证。指针别名问题不仅与结构有关,还与任何不兼容的数据类型有关。例如,您不能将指向 int 的指针转换为指向双精度的指针。
  • Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members. 我想我是从之前的回答中读到的。
【解决方案2】:

您的代码有很多问题,但仅指您对结构的具体问题

Standard C 表示您的解决方案有效

6.7.2.1 结构和联合说明符

第 15 章

在结构对象中,非位域成员和位域所在的单元 驻留的地址按照声明的顺序增加。 指向 a 的指针 结构对象,经过适当转换,指向它的初始成员(或者如果该成员是 位域,然后到它所在的单元),反之亦然。可能有未命名的 在结构对象内填充,但不在其开头。

强调我的

您应该在另一个结构中使用gen_struct:这表示尊重“初始成员”的规则。

struct gen_type {
    int flag;
    void *nextaddress;
};

struct node_type_int {
    struct gen_type header;
    long data;
};

struct node_type_real {
    struct gen_type header;
    double data;
};

struct node_type_char {
    struct gen_type header;
    char data;
};

如你所见,我还更改了 nextaddress: 的类型,它是指针,所以使用指针。

旁注:do-i-cast-the-result-of-malloc

【讨论】:

  • 但是,标准 C 不保证 dataaddress = ((long)(&generic->nextaddress)) + sizeof(long); 有效。代码中也存在指针别名问题。
  • @Marian 问题是:可以称为有效链表吗?而且,它甚至是一个有效的数据结构吗? Code Review 或检查我的代码服务也不是。我指出了OP代码中与问题相关的部分。
  • @LP 据我了解,问题是关于链接中提供的特定代码。您的回答可能给人的印象是 OP 的代码是有效的,而它不是。另一方面,您的代码是有效的。
  • @Lundin 当然,但我回答了 OP 问题:它可以称为有效链表吗?而且,它甚至是一个有效的数据结构吗?
【解决方案3】:

很好。开箱即用,我喜欢不同的方式!但是……

考虑到内存对齐方式的差异,您是否觉得以下代码可能存在问题:

dataaddress = ((long)(&generic->nextaddress)) + sizeof(long);

在这里,您假设数据是连续存储的,因此通过将 sizeof long 添加到下一个地址的地址来计算数据的地址。但这不一定总是如此,对吧?

相反,我觉得最好在读取标志值之后再类型转换成合适的结构类型,然后再读取数据。

考虑到这些挑战,虽然您节省了一些内存,但增加了一些处理。因此,尽管它看起来像一个有效的链表,但可以根据内存与处理的选择来决定实现。

【讨论】:

  • 是的,这是最终点。尽管从程序员的角度来看,处理有点难以想象和实现,但与每个实例中的几个字节泄漏相比,我认为执行一些简单的加法和寻址不需要太多的处理器开销.我宁愿以多几秒钟的处理时间为代价拥有一个紧凑的内存。怎么说?
【解决方案4】:

您的代码至少有两个弱点:

1.) long 和指针之间的转换。只有当整数类型可以保持必要的范围时,C 标准才允许这样做,这在具有 32 位 long 整数和 64 位指针的体系结构上可能不是这种情况。

我正在添加 C11 标准的引用,第 6.3.2.3 节指针:

整数可以转换为任何指针类型。除先前规定外, 结果是实现定义的,可能没有正确对齐,可能不指向 引用类型的实体,并且可能是陷阱表示。67)

任何指针类型都可以转换为整数类型。除先前规定外, 结果是实现定义的。如果结果不能用整数类型表示, 行为未定义。结果不必在任何整数的值范围内 输入。

67) 将指针转换为整数或将整数转换为指针的映射函数旨在 与执行环境的寻址结构一致

2.) 实现假设两个不同结构中的字段(前面​​字段的类型匹配)存储在结构内的相同位置(偏移量)。尽管对于大多数实现来说都是如此,但标准仅在这两个结构是联合的一部分时才保证这一点。

第 6.5.2.3 节结构和联合成员:

一个特殊的保证是为了简化联合的使用:如果联合包含 几个结构共享一个共同的初始序列(见下文),如果联合 对象当前包含这些结构之一,允许检查常见的 它们中任何一个的初始部分,任何地方的完整类型的联合声明 可见。如果对应的成员,两个结构共享一个共同的初始序列 对于一个或多个序列具有兼容的类型(并且对于位域,具有相同的宽度) 初始成员。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-17
    • 2011-09-30
    • 2023-03-04
    • 2023-01-09
    • 2012-01-08
    • 2015-11-25
    相关资源
    最近更新 更多