【问题标题】:Does a simple cast to perform a raw copy of a variable break strict aliasing?执行变量原始副本的简单转换是否会破坏严格的别名?
【发布时间】:2011-02-20 20:20:40
【问题描述】:

我最近读了很多关于strict aliasing的文章。 C/C++ 标准说以下代码无效(未定义的行为是正确的),因为编译器可能在某处缓存了 a 的值,并且在我更新 b 时不会识别它需要更新该值;

float *a;
...
int *b = reinterpret_cast<int*>(a);
*b = 1;

该标准还规定char* 可以为任何内容设置别名,因此(如果我错了,请纠正我)每当对char* 变量进行写访问时,编译器都会重新加载所有缓存的值。因此下面的代码是正确的:

float *a;
...
char *b = reinterpret_cast<char*>(a);
*b = 1;

但是在根本不涉及指针的情况下呢?例如,我有以下代码,GCC 会向我抛出关于严格别名的警告。

float a = 2.4;
int32_t b = reinterpret_cast<int&>(a);

我想要做的只是复制 a 的原始值,所以不应该应用严格的别名。这里可能存在问题,还是只是 GCC 对此过于谨慎?

编辑

我知道有一个使用 memcpy 的解决方案,但是它会导致代码的可读性大大降低,所以我不想使用那个解决方案。

EDIT2

int32_t b = *reinterpret_cast&lt;int*&gt;(&amp;a); 也不起作用。

已解决

这似乎是a bug in GCC

【问题讨论】:

  • 当你写一个字符指针时,编译器不一定会重新加载所有变量,但它能够找出哪个变量发生了变化,然后重新加载那个变量。
  • 你确定最后一个例子给出了警告吗?使用 GCC 4.1.2 (-Wall -ansi -pedantic) 编译,我没有收到任何投诉。
  • 您不会从前两个示例中获得 任何 工作代码,因为您将 int* 隐式分配给 int 并将 char* 分配给 char。跨度>
  • @DeadMG:哎呀,我的错。
  • @Oli Charlesworth:您需要首先使用-fstrict-aliasing 命令行开关或使用-O2 启用严格别名

标签: c++ casting reinterpret-cast strict-aliasing type-punning


【解决方案1】:

如果你想复制一些内存,你可以告诉编译器这样做:

编辑:添加了一个功能以获得更易读的代码:

#include <iostream>
using std::cout; using std::endl;
#include <string.h>

template <class T, class U>
T memcpy(const U& source)
{
    T temp;
    memcpy(&temp, &source, sizeof(temp));
    return temp;
}

int main()
{
    float f = 4.2;
    cout << "f: " << f << endl;

    int i = memcpy<int>(f);
    cout << "i: " << i << endl;
}

[Code] [Updated Code]

编辑:正如用户/GMan 在 cmets 中正确指出的那样,功能齐全的实现可以检查 TU 是否为 PODs。但是,鉴于该函数的名称仍然是 memcpy,因此可以依靠您的开发人员将其视为具有与原始 memcpy 相同的约束。这取决于您的组织。此外,使用目标的大小,而不是源的大小。 (谢谢,奥利。)

【讨论】:

  • 我知道有一个使用 memcpy 的解决方案,但是它导致代码的可读性大大降低,所以我不想使用它。无论如何,谢谢!
  • 如果您只是想“复制一些原始内存”,那么 memcpy 不是显而易见的方式吗?这是“可读”的标准之一。你不经常这样做,是吗? :-)
  • 在我的例子中,可能有数百个小型内联包装函数与更低级别的 API 接口,是的,不幸的是,目前至少有一半打破了严格的别名。所以一个可读的解决方案会得到回报。
  • +1 是一个不错的模板。尽管使用sizeof(dest) 作为memcpy 参数可能更安全。
  • 不确定那里模板的使用...对我来说这意味着您可以传递任何类型的 C++ 类,即使发生的事情只用 POD 对象定义,一切都会很复杂。
【解决方案2】:

基本上,严格的别名规则是“未定义以不同于其声明类型的类型访问内存,但作为字符数组除外”。所以,gcc 并不过分谨慎。

【讨论】:

  • 但据我了解,我的情况并没有破坏严格的别名,因为 &amp;ba 不引用相同的内存位置。
  • b 被初始化为 a 的内容被解释为一个 int 而它是一个浮点数,打破了严格的别名规则。如果您将 reinterpret_cast 结果提供给需要 int32_t 值的东西,您应该会得到相同的错误。
  • 是的,我同意,特定的内存访问确实会破坏严格的别名。但是,如果我们将上下文考虑在内,会有什么问题吗?编译器可以在严格别名下推迟对a 的任何写入吗?
  • @jons34yp,我对规则的理解是:是的。
  • 编译器可以(并且显然可以)在严格别名没有被破坏的假设下进行优化。如果您写入变量,但没有(明显地)从中读取,编译器可能会完全删除它。至少看起来编译器对代码有点困惑。这绝不是一件好事!
【解决方案3】:

如果这是您经常需要做的事情,您也可以只使用联合,恕我直言,对于此特定目的,它比强制转换或 memcpy 更具可读性:

union floatIntUnion {
  float a;
  int32_t b;
};

int main() {
  floatIntUnion fiu;
  fiu.a = 2.4;
  int32_t &x = fiu.b;
  cout << x << endl;
}

我意识到这并不能真正回答您关于严格混叠的问题,但我认为这种方法使代码看起来更清晰,并更好地显示您的意图。

并且还要意识到,即使正确地复制,也不能保证你得到的int 将对应于其他平台上的相同float,所以计算这些浮点数的任何网络/文件 I/O/如果您打算创建一个跨平台项目,请输入。

【讨论】:

  • 我相信,根据严格的 C++ 标准,这种构造(访问联合的两个字段)是未定义的行为;并且可能也打破了严格的混叠。见stackoverflow.com/questions/4328342/…
  • 我使用-fstrict-aliasing -Wall -pedantic 没有收到任何消息,但你说得对,它在技术上是 UB。话虽如此,您很难找到表现不同的编译器。
  • "我没有收到任何消息",因为 GCC 决定明确定义这种特殊情况(但不清楚哪一组程序是 GCC 定义的)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-23
  • 1970-01-01
  • 1970-01-01
  • 2013-06-29
  • 2022-10-15
  • 2020-01-09
  • 1970-01-01
相关资源
最近更新 更多