【问题标题】:"dereferencing type-punned pointer will break strict-aliasing rules" warning“取消引用类型双关指针将破坏严格的别名规则”警告
【发布时间】:2010-11-12 09:20:53
【问题描述】:

我使用了一个代码,将 enum* 转换为 int*。像这样的:

enum foo { ... }
...
foo foobar;
int *pi = reinterpret_cast<int*>(&foobar);

编译代码(g++ 4.1.2)时,我收到以下警告消息:

dereferencing type-punned pointer will break strict-aliasing rules

我在 google 上搜索了这条消息,发现它仅在启用严格别名优化时才会发生。我有以下问题:

  • 如果我留下带有此警告的代码,是否会生成潜在的错误代码?
  • 有没有办法解决这个问题?
  • 如果没有,是否可以从源文件内部关闭严格别名(因为我不想为所有源文件关闭它,也不想为这个源文件)?

是的,我确实需要这种别名。

【问题讨论】:

  • 你为什么不直接static_cast&lt;int&gt;(foobar)
  • 这个answer 看起来有你要找的东西。

标签: c++ warnings strict-aliasing


【解决方案1】:

按顺序:

  • 是的。 GCC 将假定指针不能别名。例如,如果您通过一个分配然后从另一个读取,作为优化,GCC 可能会重新排序读取和写入 - 我在生产代码中看到过这种情况,调试起来并不愉快。

    李>
  • 几个。您可以使用联合来表示您需要重新解释的内存。您可以使用reinterpret_cast。您可以在重新解释内存时通过char * 进行转换-char * 被定义为能够为任何东西起别名。您可以使用具有__attribute__((__may_alias__)) 的类型。您可以使用 -fno-strict-aliasing 全局关闭别名假设。

  • __attribute__((__may_alias__)) 在所使用的类型上可能是最接近禁用特定代码部分的假设的方法。

对于您的特定示例,请注意枚举的大小定义不明确; GCC 通常使用可用于表示它的最小整数大小,因此将指向枚举的指针重新解释为整数可能会在结果整数中留下未初始化的数据字节。不要那样做。为什么不直接转换为适当大的整数类型?

【讨论】:

  • 只是一个警告,根据 C++ 标准,通过写入一个成员并从另一个成员读取来使用联合是未指定的行为。所以它可能有效,也可能无效。
  • @JPvdMerwe:GCC 明确支持它。 MSVC 也是如此。
  • "cast via char*" - 你的观点没有错,但“via”可能会产生误导。您可以通过char*unsigned char* 逐个读取字节。但是reinterpret_cast&lt;int*&gt;(reinterpret_cast&lt;char*&gt;(&amp;foobar)) 仍然打破了严格的别名,即使我已经“通过char* 投射”。如果 X 可以别名 Y,Y 可以别名 Z(Y 是 char*),那么在严格的别名规则下,不一定遵循 X 可以别名 Z。
  • @JPvdMerwe:查看stackoverflow.com/questions/8511676/…的接受答案
  • 通过 static_casting 避免 reinterpret_cast 到 void* 然后到所需的类型将不会产生警告。但是,它仍然会调用未定义的行为吗?
【解决方案2】:

您可以使用以下代码来转换您的数据:

template<typename T, typename F>
struct alias_cast_t
{
    union
    {
        F raw;
        T data;
    };
};

template<typename T, typename F>
T alias_cast(F raw_data)
{
    alias_cast_t<T, F> ac;
    ac.raw = raw_data;
    return ac.data;
}

示例用法:

unsigned int data = alias_cast<unsigned int>(raw_ptr);

【讨论】:

  • static_assert(sizeof(T) == sizeof(F), "Cannot cast types of different sizes"); 添加到alias_cast 将提高此代码的安全性。
  • 如果在T和F是指针类型的地方使用这个,这不还是UB吗?如果它们是非指针类型,这不是和reinterpret_cast&lt;T&gt;(f) 一样,没有任何额外的检查吗?
【解决方案3】:

但是你为什么要这样做呢?如果 sizeof(foo) != sizeof(int) 它将中断。仅仅因为枚举就像一个整数并不意味着它被存储为一个。

所以是的,它可能会生成“潜在的”错误代码。

【讨论】:

  • 我检查了,它们的大小相同。
  • @petersohn 也许在你的机器上,这并不意味着什么。
  • @petersohn:现在呢?它们的尺寸仍然相同吗?你不能保证仅仅因为它在某一时刻工作,它就会继续工作。
  • @jalf:是的,它们的大小仍然相同。它是实现定义的枚举的底层类型是什么,而不是未指定。我实际上并没有检查 GCC 关于这个主题的文档,但我很确定他们不会说“周一、周三和周五是 int,但周二、周四、周末和公共假期是短的”。所以我强烈怀疑petersohn实际上确实有保证,对于他说他正在使用的编译器,可能会随着平台的变化而变化。只是没有人费心去查找和引用它。
  • 我可以设法更改实现,以便现在在 enum 和 int 之间进行 static_cast 就足够了,而不是 enum* 和 int*。
【解决方案4】:

你看过this answer吗?

严格的别名规则使得这 设置非法,两种不相关的类型 不能指向同一个内存。 仅限 char* 有这个权限。 不幸的是,您仍然可以编写此代码 方式,也许会收到一些警告,但有 它编译得很好。

【讨论】:

    【解决方案5】:

    严格别名是一个编译器选项,因此您需要从 makefile 中将其关闭。

    是的,它会生成不正确的代码。编译器将有效地假设foobarpi 没有绑定在一起,并且假设*pi 不会在foobar 发生变化时发生变化。

    如前所述,请改用static_cast(并且没有指针)。

    【讨论】:

      猜你喜欢
      • 2016-12-26
      • 1970-01-01
      • 2011-11-29
      • 1970-01-01
      • 2017-09-22
      • 2014-12-30
      • 1970-01-01
      • 2019-09-22
      • 1970-01-01
      相关资源
      最近更新 更多