reinterpret_cast<>和可移植性

7

我读过reinterpret_cast<>如果使用不当可能很危险。所以我相信我一直在正确使用它 ;)。我发现如果我有模板类并且需要类型转换,那么使用它是很好的选择。但最近我读到reinterpret_cast<>也是不可移植的。对此我感到很遗憾。原因是什么?请看下面的代码:

void Disp(int* val)
{
    for (int i=0; i < SZ; ++i)
    {
        cout << *(val+i) << " ";
    }
    cout << endl;
}

int main()
{
    int arr[SZ];
    Disp(arr);

    unsigned char* ptr = reinterpret_cast<unsigned char*>(arr);
    for (unsigned char* i = ptr; i < (ptr + (SZ * sizeof(int))); i++)
    {
        *i = 0;
    }
    Disp(arr);
    return 0;
}

现在是输出内容:
1174214872 32767 4196789 0 568392584 58 4196720 0 0 0 
0 0 0 0 0 0 0 0 0 0 

Machine type: Linux 2.6.32-358.11.1.el6.x86_64 #1 x86_64 x86_64 x86_64 GNU/Linux

975580 -16506540 -13369152 0 -4202936 67876 3 -4202836 4 -4202828 
0 0 0 0 0 0 0 0 0 0 

Machine type: SunOS DELPHI 5.10 Generic_142900-01 sun4u sparc SUNW,Netra-240

我复制了同一个程序在Linux和Solaris系统下的输出结果。对于可移植性问题我还是个新手,所以请问一下,如果我的代码中类似这样的语句,会引起任何可移植性问题吗?即使不是在这段代码中,当代码变得越来越复杂(包括动态分配等),长时间运行时是否会出现一些意外情况(未定义行为)?感谢您的帮助。


请参考此答案:https://dev59.com/8FPTa4cB1Zd3GeqPkZBh#4748348 指针类型之间的转换是可行的。 - Barmar
我敢打赌,在某些机器上,sizeof(int) 不等于 4*sizeof(unsigned char) - ChronoTrigger
1
不要硬编码 4,你应该使用 sizeof(int) - Barmar
我已经修改了。谢谢。 - sarath
2
不要计算数组的大小,使用 i < ptr + sizeof arr。或者直接使用 i < 1[&arr] - Ben Voigt
4个回答

7

reinterpret_cast<> 的可移植性问题在于不同的 CPU 在内存中以不同的方式存储数字。有些 CPU 从最不重要的字节开始存储(小端),有些则恰恰相反(大端)。还有一些使用一些奇怪的字节顺序,比如 1 0 3 2,至于为什么我也不知道。

总之,这意味着只要你没有以任何方式依赖字节顺序,reinterpret_cast<> 就是可移植的。

你的示例代码并没有依赖字节顺序,它对所有字节都一视同仁(将它们设置为零),因此该代码是可移植的。如果你想在同一台计算机上使用 reinterpret_cast<> 复制某个数据对象(而不解释字节),那么该代码也是可移植的(memcpy() 也可以实现这点)。

不可移植的是像查看第一个字节以确定数字符号的内容(仅适用于大端机器)。如果你只是通过发送 reinterpret_cast<char*> 结果来在两台计算机之间传输数据,那么你也会遇到麻烦:目标机器可能使用与源机器不同的字节顺序,从而完全错误地解释你的数据。

我认为说 reinterpret_cast<> 是不可移植的是错误的,它只是将机器细节暴露给 C++ 代码,这些细节是特定于机器的。任何依赖于这个机器细节的代码都是不可移植的。


5

这段代码存在一些问题。

  • 你正在显示未初始化数组的内容,这实际上没有任何意义。
  • 你在硬编码循环的大小,假设sizeof(int) == 4。如果你在机器上运行的是不同大小的int,那么这将是非可移植的。
  • 如果你需要用除零以外的任何值来填充内存,则需要考虑处理器的字节序。

编辑:我看到你已经删除了硬编码的神奇常数4,所以第二点不再适用。


除了使用其他值进行初始化外,我还需要担心其他什么吗?我的意思是,您能指出一些reinterpret_cast<>变得不可移植的其他情况吗? - sarath
1
@Sarath,将char *强制转换为类型是一种通用的方法,因此您可以避免出现很多问题。更麻烦的是,在对象之间进行强制转换可能会因编译器或设置不同而导致布局不同。或者反过来,即将char *强制转换为int *,在某些处理器上可能会遇到对齐问题。 - Mark Ransom

0

我认为这是可以的。有人争论说,由于在 [basic.lval]#10 中允许的别名列表不包括使用 int 别名写入通过 char 类型写入的值,因此它是未定义的。(相反的情况是包括的)。

然而,我解释“对象的存储值”仍然指的是 int 值,即使是通过 char 别名写入的。这似乎在 C++ 标准中没有明确表达;但在 C99 中(这些规则显然是从中派生出来的),它具有涉及“有效类型”的不同措辞,指定内存的有效类型仍然是 int,尽管它是通过 char 别名写入的。

澄清一下,在两种语言中,还有其他规则涵盖了是否创建陷阱表示的问题。但我看到有人争论说,在这种情况下,C++ 中的严格别名规则优先于那些规则。


0
使用reinterpret_cast<>的可移植性/实用性问题会非常具体化,取决于它的使用方式。得到一个玩具示例的答案无法帮助你解决实际上可能遇到需要使用它的情况。
一般来说,如果有一种方法可以不使用reinterpret_cast<>来完成某件事情,那么你应该采用这种方法。使用你问题中的示例,你可以通过使用int*来将数组清零,而没有使用reinterpret_cast<>,即使我认为你问题中使用reinterpret_cast<>的方式事实上是定义良好和可移植的 - 使用已被重新解释为char*的指针是少数可以实现可移植的领域之一。
在大多数需要使用reinterpret_cast<>的情况下,你正在处理的是真正不可移植的东西,你应该理解为什么需要在特定情况下使用reinterpret_cast<>并了解与该特定情况相关的可移植性问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接