【问题标题】:Does accessing array of POD struct as array of its single member violate strict aliasing?将 POD 结构的数组作为其单个成员的数组访问是否违反了严格的别名?
【发布时间】:2015-02-15 18:20:18
【问题描述】:

我有用于访问不相关数据存储(即句柄)中的数据的整数值。我选择将整数包装在一个结构中,以便拥有强类型对象,这样不同的整数就不会混淆。它们是而且必须是 POD。这就是我正在使用的:

struct Mesh {
    int handle;
};
struct Texture {
    int handle;
};

我有这些句柄的数组,例如:Texture* textureHandles;

有时我需要将一组句柄作为int* 传递给代码的更通用部分。现在我正在使用:

int* handles = &textureHandles->handle;

它本质上接受一个指向结构的第一个元素的指针并将其解释为一个数组。

我的问题基本上是这是否合法,或者它是否违反了严格的别名来操纵 int* handlesTexture* textureHandles 指向同一内存。我认为应该允许这样做,因为在这两种情况下都以相同的方式访问底层类型 (int)。我的保留与我通过获取一个结构内成员的地址来访问多个结构这一事实有关。

作为我第一个问题的延伸,以下可以吗?

int* handles = reinterpret_cast<int*>(textureHandles);

【问题讨论】:

  • 你想使用结构来获得强类型,然后你想抛弃类型来获得 int。你会得到两全其美的结果。
  • @NeilKirk 只有非常具体的函数才会使用原始 int* 数组。其余的将使用类型化结构。它们只是为了避免在一般情况下使用手柄时出错。
  • 我认为你应该告诉我们更多关于你的实际项目,因为你的设计很奇怪。
  • @Neil Kirk 假设他正在使用 OpenGL(可能与 Direct3D 相同)。使用强类型会对您有所帮助,但归根结底,您仍然必须将 int(或 int 数组)传递给 API。如果它们是二进制相同的,则将 Texture 数组复制到 int 数组是不可取的。
  • @NeilKirk 这有什么奇怪的?当对象以一种语言出现在代码中但必须从另一种语言访问时,我在语言之间的所有接口中都做几乎完全相同的事情。 swig 生成的接口或多或少做同样的事情。 (句柄类型可能会有所不同:我的声明为 void*,而 IIRC,swig Java 接口使用 long long。但想法是相同的:对象由句柄表示,句柄是某种允许查找的魔术 cookie对象。)

标签: c++ c++11 struct c++03 strict-aliasing


【解决方案1】:

reinterpret_cast&lt;int*&gt;(textureHandles) 绝对和&amp;textureHandles-&gt;handle 一样好。标准中有一个特殊的例外,甚至从 C 继承而来,它表示一个指向标准布局结构的指针,经过适当转换,指向该结构的初始成员,反之亦然。

使用它来修改句柄也可以。它不违反别名规则,因为您使用int 类型的左值来修改int 类型的子对象。

增加结果指针,并使用它来访问Texture 对象数组中的其他元素,但有点不确定。 Jerry Coffin 已经指出sizeof(Texture) &gt; sizeof(int) 是可能的。但是,即使sizeof(Texture) == sizeof(int),指针算法也只为指向数组的指针定义(其中任意对象可能被视为长度为 1 的数组)。您在任何地方都没有 int 的数组,所以添加只是未定义的。

【讨论】:

  • 你确定它不违反别名规则吗?如果我使用 int 指针写入成员,然后通过 Texture 指针读取成员怎么办?
  • @NeilKirk 还是可以的。对于Texture 类型的对象,您有一个Texture 类型的左值,对于int 类型的子对象,您有一个int 类型的左值。那里没有别名问题。
【解决方案2】:

不,这不能保证有效。特别是,允许​​编译器在结构的任何元素之后插入填充,但不允许在数组元素之间插入填充。

也就是说,结构只有一个元素(类型为int,或至少与long 一样大的元素),大多数编译器不会插入任何填充的可能性很大,所以你的作为一般规则,当前使用可能相当安全。

【讨论】:

  • 你没有讨论严格的别名,我认为这会是一个问题,但我不是专家。
  • 对于这个问题,我只对结构包含单个成员的特定情况感兴趣。在这种情况下,编译器真的允许插入填充吗?
  • @rasmus:是的。它不能在元素之前插入填充,但可以在它之后。
  • @NeilKirk:在这种情况下,我认为没有真正的理由讨论严格的别名规则。我提出的观点足以回答这个问题。
  • 感谢您的回答。我最初接受了它,但经过一番思考后,我决定采用 hvd 的答案,因为它还讨论了我特别询问的严格别名。有时我希望我能接受两个答案。至少你得到了我的 +1
【解决方案3】:

肯定违反了严格的别名,如果函数可以访问 通过int*Mesh*Texture* 的数组,您可以 很好地遇到了问题(尽管可能只有在它修改了 以某种方式排列)。

根据你对问题的描述,我不认为规则 严格的混叠确实是您所关心的。真正的问题 是编译器是否可以向不是的结构添加填充 存在于int,因此sizeof( Mesh ) &gt; sizeof( int )。和 虽然答案是肯定的,但我无法想象一个编译器会 至少在今天这样做,并且至少使用 int 或更大的类型 struct。 (一个词寻址的机器可能会添加填充到 struct 仅包含 char。)

真正的问题可能更多的是通用代码是否是 遗产,不能改变,也不能改变。否则,显而易见的解决方案 是创建一个通用句柄类型:

struct Handle
{
    int handle;
};

然后要么从中派生你的类型,要么使用reinterpret_cast 正如你所提议的。有(或至少有)保证允许 通过指向不同的指针访问struct 的成员 struct,只要成员和所有前面的成员都是相同的。 这就是您在 C 中模拟继承的方式。即使保证 已被删除——这是它出现在 C++ 中的唯一原因 是出于 C 兼容性的原因——没有编译器敢违反 考虑到依赖它的现有软件的数量。 (这 例如 Python 的实现。几乎所有的 Python 插件,包括那些用 C++ 编写的。)

【讨论】:

  • 通过int 类型的左值访问定义为int 的对象或子对象并不违反严格的别名,而且我真的不明白你怎么可能提出合法的论点。你能详细说明一下吗? (您指出的保证实际上并不能保证您认为它的作用,即使在 C 中也是如此。它仅适用于联合。这里有一个不同的保证确实有帮助,即指向标准布局结构的指针指向其初始成员。)
  • 注意:如果反过来,我会完全同意您的回答。给定一个任意的int 对象,尝试像Handle 一样访问它不是一个好主意,无论它是否在数组中。但这不是 OP 所要求的。
  • 没有部分代码是遗留的。有问题的代码用于将句柄映射到数据,并且能够一次将多个句柄插入到映射中(因此是 int 数组)。但是有了这些很棒的答案中的信息,我可能会重新考虑这部分设计。对 Handle 进行子类化的问题是键入的句柄不再是 POD。这是我的要求。
  • @hvd 你有一个有趣的观点。根据标准,编译器可以假定int*Texture* 没有别名。但是编译器必须假定int* 可以与p-&gt;handle 别名(其中pTexture*)。关于仅适用于工会的保证:您可能在形式上是对的(我在发布时找不到带有此保证的实际文本),但实际上,大量代码(如 Python)在转换时依赖于它的工作指针也是如此。 (可能有限制。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-24
  • 2020-04-11
  • 2012-04-02
  • 1970-01-01
  • 1970-01-01
  • 2017-05-08
相关资源
最近更新 更多