【问题标题】:Struct Extension in CC中的结构扩展
【发布时间】:2020-08-21 07:48:12
【问题描述】:

假设定义了 2 个结构,例如:

typedef struct {
   T x;
   T y;
} A;

typedef struct {
   A a;
   T z;
} B;

我可以将指向结构 B 的指针视为指向结构 A 的指针吗?

在实践中这是可靠的/标准的/可移植的/编译器不变的:

B b = {{1,2},3};
A * a = &b;

print(a->x);
print(a->y);

【问题讨论】:

  • 嗯,您应该能够可靠地将 *B 转换为 *A,因为如 C99 标准第 6.7.2.1 §13 节所述:“指向结构对象的指针,适当地转换后,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。”。但是,我认为A *a = &(b->a) 更干净。
  • 总是尝试编写明显正确的干净代码。只有当您拼命尝试优化最后纳秒的性能时,您才能考虑应用技巧,例如将一种类型的结构解释为另一种类型的结构
  • @4386427 当以骗子身份关闭时,不一定要让最旧的一个打开。链接中接受的答案真的比我发布的更好吗?它没有引用任何内容,它只是一个“相信我”的答案,没有任何例子。我们应该努力保持质量,而不是保留“西部最快的枪”。我显然是偏心的,因为我发布了一个答案,所以我不会重新打开。
  • @Lundin 好吧,在我作为 dup 关闭时,我没有看到两个 Q/A 之间有任何显着的质量差异。双方都表示这是合法的。两者都引用了标准的第 6.7.2.1 节。区别 - 正如我所看到的 - 是 1)在这个 Q/A 中,引用在 A 中,而它在 dup 中的 Q 中,2)这个 Q/A 包含的标准文本比重复。如前所述 - 我不认为这是一个显着的差异。无论如何 - 我不介意有人把它转过来,这样另一个现在关闭了,而这个打开了。
  • @4386427 一切都好,只要考虑内容而不是发布日期。

标签: c pointers struct compilation


【解决方案1】:

C17 6.7.2.1 声明了这一点(强调我的):

在结构对象中,非位域成员和位域所在的单元 驻留的地址按照它们被声明的顺序增加。 指向 a 的指针 结构对象,经过适当转换,指向其初始成员(或者如果该成员是 位域,然后到它所在的单元),反之亦然

这意味着您必须将B b 对象的指针“适当地转换”为它的第一个成员的类型。这种转换不会隐式发生,您必须通过显式转换来做到这一点:

A * a = (A*)&b;

按照上面引用的部分,这样做是明确且安全的。

同样,编译器也不允许假定指向A 的指针和指向B 的指针没有别名。有效类型 6.5.7(“严格别名”)的规则为这种情况提供了一个例外:

对象的存储值只能由具有以下类型之一的左值表达式访问:
...

  • 在其成员中包含上述类型之一的聚合或联合类型

例如,在优化期间,调用函数 void func (B* b) 的编译器不允许假定在其他翻译单元中定义的外部语言变量 extern A a; 没有被函数更改。

【讨论】:

  • 这就是你通常在 C 中实现 OO 继承的方式。如果 A 包含一个函数指针,任何以 A 作为其第一个成员的结构都可能覆盖该函数指针以实现多态性,其中a->func(); 将调用继承的方法。
  • 谢谢,我只是在探索有关如何在 C 中模拟继承的选项。因此,您必须显式转换指针的原因是不能保证结构的第一个成员实际上位于0 偏移量?
  • @FedericoDevigili 第一个成员保证在 0 偏移并对齐。但是,在大多数情况下,编译器无法在不同的指针类型之间进行隐式转换,因为赋值规则不允许这样做。这就是你需要投射的原因。
猜你喜欢
  • 2014-08-22
  • 2014-04-08
  • 1970-01-01
  • 2015-08-05
  • 1970-01-01
  • 1970-01-01
  • 2017-09-01
  • 2023-03-30
  • 1970-01-01
相关资源
最近更新 更多