【问题标题】:Can I safely convert struct of floats into float array in C++? [duplicate]我可以在 C++ 中安全地将浮点结构转换为浮点数组吗? [复制]
【发布时间】:2017-08-26 17:51:14
【问题描述】:

例如我有这个结构

struct  A {
    float x;
    float y;
    float z;
};

我可以这样做吗? A a; float* array = (float*)&a; 并使用 as 浮点数组?

【问题讨论】:

  • 不,变量之间可能有填充。
  • 我的意思是在某处放置一个static_assert(sizeof (A) == sizeof (float) * 3, "blah")。然后,如果你的编译器决定添加填充,你会得到一个很好的错误。但我有 99% 的把握它不会这样做。
  • @HolyBlackCat - 它可能会导致比你想象的更多的错误。在具有 4 字节浮点数的 64 位机器上,编译器很可能会在结构的末尾添加填充,以使结构与机器字边界对齐。
  • @StoryTeller 我认为如果编译器只在最后添加填充没有问题,是吗?
  • @SemyonTikhonenko - 这是一个问题,因为静态断言会触发。尽管所有“临时”的人都会正确地说您的 hack 会起作用。

标签: c++


【解决方案1】:

不,将struct 类型转换为数组将不起作用。允许编译器在成员之间添加 padding

数组成员之间没有填充。

注意:没有什么能阻止你进行转换,但是,在转换后使用值会导致未定义的行为。

【讨论】:

  • 在一般意义上你可能是对的,但从实际的角度来看,这几乎适用于我使用的 Windows、Linux 和嵌入式平台(如 PowerPC 和 arm)下的所有编译器。我当然希望看到一个失败的例子:)
  • @Sil 将结构类型转换为数组可能有效。不过,就托马斯而言,我不会称这样做 安全 并且 OP 确实部分询问是否可以安全地完成。
  • 顺便说一句,我希望 Thomas 说这种方式不安全,而不是说它不起作用。
  • @Louis 他问他是否可以这样做....从一个角度来看他可以,但在一般/严格/正式的意义上,不,他不能。就像你可以问“1/x 是在实数上定义的吗?”好吧,大多数时候是(我的回答)。但是一个严格的数学答案是否定的,它不是(你的答案)只是因为在所有其他值中看到的 0 值:P 同样在我们的例子中你找不到 0,可以吗? (除非你做了一些 pragma pack 8,否则它会失败:P)
  • @Sil 它被称为“未定义的行为”。在墨菲先生的大力支持下,编译器可以邮寄给您。但前提是您为代码的生产版本打开优化器。所以它可以在您的开发机器上正常工作,并停止生产。或者销毁您最重要客户的所有数据。
【解决方案2】:

在实际意义上,是的,您可以这样做,它适用于所有最常用的架构和编译器。

参见维基百科上的"Typical alignment of C structs on x86" 部分。

更多细节:

  • 浮点数为 4 个字节,不会插入任何填充(几乎所有情况)。

  • 大多数编译器还可以选择指定结构的打包,您可以强制不插入任何填充(即 Visual Studio 中的 #pragma pack)

  • 保证数组在内存中是连续的。

你能保证它可以在世界上所有的 CPU 上和所有的编译器一起工作吗?不..但我绝对希望看到一个失败的平台:)

编辑:如果有填充字节,添加 static_assert(sizeof(A) == 3*sizeof(float)) 将使此代码无法编译。这样你就可以确定它在编译时可以工作。

【讨论】:

  • 我喜欢这个答案,因为它更喜欢实用主义而不是规范的迂腐应用。
  • 我可以想象一个比较晦涩的 64 位平台出于对齐原因做一些奇怪的事情,你可以have issues if any of the data is uninitialized(是的,即使你全局关闭了 sNaN)。当然,您当然不能假设“x86 上的典型对齐方式”将描述 x86 以外的任何架构。
  • +1。这可能是未定义的行为,但您会发现 很多 以这种方式编写的旧 C 代码。微软甚至在 VC++ 文档中包含了类似的示例代码。
  • @Kevin 不错的文章(我不熟悉 ia64),但它不适用于这里。添加一个 static_assert(sizeof(A) == 3*sizeof(float)) 将使代码可移植,如果它编译:)
  • static_assert 的更清晰版本:static_assert(sizeof(A) == sizeof(float[3]))
【解决方案3】:

这是一个严格的别名违规,简单明了。在您的情况下,使用该指针对除第一个元素以外的任何元素的任何访问都是未定义的行为。在更复杂的情况下,无论您访问哪个元素,它都只是简单的未定义行为。

如果需要数组,请使用数组。 std::array 也有 std::get 的重载,因此您可以使用它来命名每个单独的数组成员:

using A = std::array<float, 3>;

enum AElement { X, Y, Z };

int main() {
  A a;
  get<X>(a) = 3.0f; // sets X;

  float* array = a.data(); // perfectly well defined
}

【讨论】:

  • 我不认为这是一个严格的别名违规,因为指针和结构元素都是floats
  • @Jeff - 这不是意见问题。 C++ 标准提到只有某些类型可以相互别名,除非另有说明,否则所有其他别名都是 UB。这里第一个成员变量的情况只是一个例外,因为它的第一个字节恰好是结构的第一个字节(在这种情况下)。
  • @Jeff - Erm,不。仅允许访问 first 成员,因为这是标准布局类型。该子句并不意味着可以将结构视为数组,即使没有填充也是如此。这里相关的是reinterpret_cast的描述,这是这里正在进行的演员。
  • @PasserBy - 因此我的观点是,由于标准布局类型保证,第一个成员还可以。但这就是标准本身所能达到的程度。
  • @PasserBy:该标准允许您将指针指向第一个成员,并进一步允许您将任何指针视为单元素数组。但是 OP 不想要一个元素数组,他们想要一个三元素数组。标准中没有任何内容允许这样做,因为现在我们不再谈论 first 成员了。
【解决方案4】:

对于 g++,您可以为结构使用属性,如下所示:

struct  A {
    float x;
    float y;
    float z;
}__attribute__((__packed__));

这是禁用结构对齐。

【讨论】:

    【解决方案5】:

    您可以这样做,直到编译器开始优化,然后出现问题。

    使用指向第一个元素的指针访问结构的第一个元素以外的任何元素都是未定义的行为。 “未定义的行为”意味着任何事情都可能发生。编译器可能假设没有未定义的行为。

    编译器可以从中推断出许多结果:如果编译器知道您的 float* 指向结构的第一个元素,那么它可以推断出该数组的每个索引都等于 0(因为其他任何内容都未定义行为)。如果编译器不知道这一点,那么它可以推断出数组指针不能指向结构,改变数组元素不能改变结构元素,反之亦然。

    你能看出这会如何出错吗?

    【讨论】:

    • 让 C 在 1980 年代和 1990 年代赢得“语言大战”的原因之一是实现支持有用的语义,而这些语义在其他语言中是不可用的。不幸的是,即使标准没有要求这种语义有用的实现以明显的方式支持它们,标准的作者认为没有必要定义一种“官方”方式来要求这种语义。但是,我认为您的帖子可能会受益于更具体和更现实的示例,例如假设它可以保持一个结构成员的值被缓存...
    • ...在寄存器中使用从不同成员计算的地址访问同一存储。 C 和 C++ 有一些不同的规则,但由于 C++ 旨在成为 C 的超集,相同的历史问题在 C++ 中仍然存在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-02
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-20
    • 2021-09-20
    相关资源
    最近更新 更多