问题 访问POD结构数组作为其单个成员的数组是否违反了严格的别名?


我有整数值,用于访问不相关的数据存储中的数据,即句柄。我已选择将整数包装在结构中以便具有强类型对象,以便不能混合不同的整数。他们是,而且必须是POD。这就是我正在使用的:

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

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

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

int* handles = &textureHandles->handle;

它基本上采用指向结构的第一个元素的指针并将其解释为数组。

我的问题基本上是这是合法的,还是违反了操纵的严格别名 int* handles 和 Texture* textureHandles 指着同样的记忆。我认为这应该被允许,因为基础类型(int在两种情况下都以相同的方式访问。我的保留与我通过获取一个结构中的成员的地址来访问多个结构的事实有关。

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

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

2264
2018-02-15 18:20


起源

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


答案:


reinterpret_cast<int*>(textureHandles) 绝对是一样好 &textureHandles->handle。标准中有一个特殊的例外,它继承自C even,它表示指向适当转换的标准布局结构的指针指向该结构的初始成员,反之亦然。

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

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


10
2018-02-15 18:30



你确定它没有违反别名规则吗?如果我使用int指针写入成员,然后通过Texture指针读取成员怎么办? - Neil Kirk
@NeilKirk那还不错。你有一个左值的类型 Texture 对于类型的对象 Texture和一个类型的左值 int 对于类型的子对象 int。那里没有别名问题。
stackoverflow.com/a/28529680/2068573 - Neil Kirk


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

也就是说,只有一个元素的结构(类型 int或者至少与某些东西一样大的东西,比如 long),大多数编译器都不会插入任何填充的机会非常好,所以你当前的用法可能就是这样 相当 安全作为一般规则。


5
2018-02-15 18:27



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


它当然违反了严格的别名,如果该功能可以访问 阵列都通过 int* 和a Mesh* 或者a Texture*, 你可以 很好地遇到了问题(虽然可能只有它修改了 以某种方式的数组)。

从你对问题的描述,我不认为是规则 严格的别名确实是你所关心的。真正的问题 是编译器是否可以向不是的结构添加填充 出现在 int, 以便 sizeof( Mesh ) > sizeof( int )。和 虽然答案是正式的,我无法想象一个编译器会 这样做,至少在今天,至少在 int或更大的类型 struct。 (一个字处理机器可能会添加填充到一个 struct 其中只包含 char。)

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

struct Handle
{
    int handle;
};

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


1
2018-02-15 18:40



它不违反严格别名来访问定义为的对象或子对象 int 通过类型的左值 int,我真的不知道你怎么可能做出合法的论证。你能详细说说吗? (您指出的保证实际上并不能保证您认为它的作用,即使在C语言中也是如此。它仅适用于工会。有一种不同的保证在这里有帮助,指向标准布局结构的指针指向其初始值会员。)
注意:如果是相反的话,我会全心全意地同意你的回答。给予任意 int 对象,试图访问它,就像它是一个 Handle 这不是一个好主意,不管它是否在阵列中。但这不是OP所要求的。
代码的任何部分都不是遗留的。有问题的代码用于将句柄映射到数据,并且能够一次性将多个句柄插入到映射中(因此 int 阵列)。但是根据这些令人敬畏的答案中的信息,我可能会重新考虑这部分设计。对Handle进行子类化的问题是,键入的句柄将不再是POD。这对我来说是个要求。 - rasmus
@hvd你有一个有趣的观点。根据标准,编译器可以假设一个 int* 和a Texture* 不要别名。但编译器必须假设 int* 可以别名 p->handle (哪里 p 是一个 Texture*)。关于仅适用于工会的保证:你可能是正式的(当我发布时我找不到具有此保证的实际文本),但在实践中,大量代码(如Python)依赖于它在投射时工作指针也是如此。 (可能存在限制。) - James Kanze
@JamesKanze如果我没记错的话,Python建立在GCC上 -fno-strict-aliasing 因为GCC确实以不适用于Python的方式进行优化。