Visual C++ ARM 迁移的常见问题

同一 Visual C++ 源代码与在 x86 或 x64 体系结构可能导致 ARM 体系结构的不同的结果执行。

迁移源问题

许多问题可能会遇到时要迁移从 x86 的代码或对 ARM 体系结构的 x64 体系结构与调用可能未定义的源代码构造,实现中定义或未指定的行为有关。

  • 未定义的行为
    C++ 标准不定义,因此,由操作引起的没有合理结果为示例的行为,将浮点值转换为无符号整数或转换为负数或超过位的数目在其提升的类型的值由很多位置。

  • 实现中定义的行为
    行为 C++ 标准要求编译器供应商定义和文档。程序可以安全性依赖于实现中定义的行为,因此,即使这样做可能是不可移植的。实现中定义的行为的示例包括内置数据类型及其对齐要求的大小。可能受实现中定义的行为的操作影响的示例访问变量参数列表。

  • 未指定的行为。
    该行为 C++ 标准特意将不确定。尽管该行为是不确定,编译器实现依赖未指定的行为的特定调用。但是,没有编译器供应商的要求预先确定该结果或确保在可比的调用之间一致的行为,因此,不文档的要求。未指定的行为的示例是子表达式包括函数的参数调用的计算顺序。

其他迁移问题可以在特性化 ARM 和 x86 或与 C++ 标准不同进行交互的 x64 体系结构之间的硬件差异。例如,x86 和 x64 体系结构的系列记忆模型可以为 volatile-限定变量用于以前完成某些类型的线程间的通信的某些其他属性。ARM,但体系结构的弱的内存模型不支持此用法,也不 C++ 标准要求)。

重要说明重要事项

虽然 volatile 获取用于实现线程间的通信的有限窗体有关 x86 和 x64 的一些属性,这些附加属性是不够通常实现线程间的通信。C++ 标准建议此类通信实现使用适当的同步基元。

由于不同的平台可能不同的方式表示这些行为,移植在平台之间的软件可能很难和合理 bug,则取决于特定平台的行为。尽管其中许多行为中观察可能出现稳定,依赖它们至少是不可移植的,然后在未定义或未指定的行为情况下,也可以是错误。中被引用的行为在将来的编译器或 CPU 实现文档不应依赖于,并且可能随着。

示例的迁移问题

本文档的其余部分描述这些 C++ 语言元素不同的情况如何产生在不同的平台不同的结果。

浮点转换为无符号整数

在 ARM 体系结构,浮点值转换为 32 位整数该为整数可以表示的最近的值,如果浮点值为整数可以表示的范围。在 x86 和 x64 体系结构,将换行,如果该整数为无符号或设置为 -2147483648,如果该整数签名。这些结构都不直接支持浮点值转换为更小整数类型;相反,将对 32 位,因此,结果被截断为较小的。

对 ARM 体系结构,该和截断的组合意味着为无符号类型的转换正确该更小的无符号类型,则该 32 位整数时,但是,导致大于较小的类型可以表示的值的已截断的结果,但太小而无法该该完整 32 位整数。转换为 32 位带符号整数也可以正常饱和,但是,该类,带符号整数的截断导致 -1 正该值的 0 和负该类的值。为更小的有符号整数的转换导致不可预知的已截断的结果。

为 x86 和 x64 体系结构;如果不同,太大,一般行为和带符号整数转换的显式估价的组合无符号整数转换的上溢,将截断时,使得大多数移位的结果不可预知。

这些平台在其所处理的转换 Nan (不是数字) 为整数如何也是不同的类型。在 ARM 上,0x00000000 的 Nan 转换;在 x86 和 x64,它转换为 0x80000000。

浮点转换只能依赖于,如果您知道该值是在整数类型的范围内其强制转换为。

偏移运算符 (<< >>) 行为

在 ARM 体系结构,因此,在该模式启动重复之前,值可以左侧或右侧将转换为 255 位。在 x86 和 x64 体系结构,因此,除非该模式的源是一个 64 位变量,该架构被用于循环访问每个多线程的 32;在这种情况下,架构循环访问每个多线程的 64 在 x64 和每个多线程的 256 在 x86 上,软件实现中使用。例如,对于的 32 位变量的值为 1 x 32 个位置左移,在 ARM 结果为 0,在 x86 结果为 1,因此,在 x64 该结果也是 1。但是,因此,如果值来源是一个 64 位变量,然后在所有三个平台的结果为 4294967296,值“不包装在周围”,直到转换了 x64 的 64 个位置,或者在 ARM 和 x86 的 256 个位置。

由于超过位的数目在源类型移位运算的结果是未定义的,不需要在所有情况下编译器都具有一致的行为。例如,因此,如果移位的两个操作线程在编译时已知,编译器可以优化程序使用内部实例添加到预先计算用于 shift 然后将其替换为的结果在移位操作位置。如果表示移位位数太大或负值,内部实例的结果与 shift 表达式的结果可能不同的和执行文件同名受 CPU。

变量参数 (varargs) 行为

在 ARM 体系结构,从在堆栈上传递受对齐取决于变量的形参表。例如,一个 64 位参数在 64 位边界对齐。在 x86 和 x64,在堆栈上传递的参数不受对齐。并不严格打包。此差异可能导致与 printf 的一个 variadic 功能添加到要为 ARM 的填充的读取内存地址,如果变量的预期格式参数列表不能正确匹配,因此,即使它可能对于某些值的子集在 x86 或 x64 体系结构的工作。请看以下示例:

// notice that a 64-bit integer is passed to the function, but '%d' is used to read it.
// on x86 and x64 this may work for small values because %d will “parse” the low-32 bits of the argument.
// on ARM the calling convention will align the 64-bit value and the code will print a random value
printf("%d\n", 1LL);   

在这种情况下,bug 可通过确保修复使用正确的格式规范,以便该参数的对齐方式考虑。此代码是正确的:

// CORRECT: use %I64d for 64-bit integers
printf("%I64d\n", 1LL);

参数计算顺序

由于 ARM、x86 和 x64 处理器非常不同,它们位于不同的要求。编译器实现,并优化不同的机会。因此,与其他因素。(如调用约定和优化设置,编译器可能计算函数参数按其他体系结构的不同顺序,或者更改时其他因素。这可能导致依赖于给定计算顺序意外更改 app 的行为。

这种错误时,会发生函数的参数影响其他参数。在同一函数调用的副作用时。通常这种依赖项很容易避免,但有时遮盖由很难辩明的依赖项,或者通过运算符重载。考虑此代码示例:

  handle memory_handle;

  memory_handle->acquire(*p);

这种显式定义,但是,如果 -> 和 * 是重载运算符,则此代码会转换为内容类似的内容:

  Handle::acquire(operator->(memory_handle), operator*(p));

此外,如果在 operator->(memory_handle) 和 operator*(p)之间的依赖项,代码可能依赖于给定计算顺序,因此,即使原始代码类似于没有可能的依赖项。

volatile 关键字默认值行为

Microsoft C++ 编译器支持通过使用编译器开关,可以指定易失存储器限定符的两个不同解释。由于在这些结构,的系列记忆设计 /volatile:ms 开关选择确保严格顺序" Microsoft 扩展的变量的语义,x86 和 x64 的传统用例 Microsoft 编译器。/volatile:iso 开关选择不保证强排序的有效 C++ 标准变量的语义。

在 ARM 体系结构,该默认值为 /volatile:iso,因为 ARM 处理器具有弱顺序的内存模型,并且,由于 ARM 软件没有依赖于传统的 /volatile:ms 扩展的语义并不通常必须与执行软件的接口。但是,它仍有时很方便甚至必需的生成武器计划使用扩展语义。例如,它可能太大于端口每次使用 ISO C++ 语义的程序,或者驱动程序软件可能必须遵循旧语义正常工作。在这些情况下,可以使用 /volatile:ms 开关;但是,重新创在 ARM 目标的传统变量的语义,编译器或写入周围必须插入到 volatile 变量的每个读取的内存障碍强制执行严格顺序,可能会对性能产生负面影响。

在 x86 和 x64 体系结构,该默认值为 /volatile:ms,原因是这些结构已经创建了使用 Microsoft C++ 编译器的许多软件依赖于它们。当您生成 x86 和 x64 程序时,可以指定 /volatile:iso 开关帮助避免使用传统变量的语义不必要的关系和提升可移植性。

请参见

其他资源

配置 ARM 处理器的程序 (Visual C++)