我正在将代码从 ICU 58.2 移植到 ICU 59.1,他们将字符类型从 uint16_t 更改为 char16_t。我打算在需要转换类型的地方直接执行 reinterpret_cast,但发现 ICU 59.1 实际上提供了这种转换的功能。我不明白的是为什么他们需要在进行 reinterpret_cast 之前使用这种抗锯齿屏障。

#elif (defined(__clang__) || defined(__GNUC__)) && U_PLATFORM !=
U_PF_BROWSER_NATIVE_CLIENT
#   define U_ALIASING_BARRIER(ptr) asm volatile("" : : "rm"(ptr) : "memory")
#endif

...
    inline const UChar *toUCharPtr(const char16_t *p) {
#ifdef U_ALIASING_BARRIER
    U_ALIASING_BARRIER(p);
#endif
    return reinterpret_cast<const UChar *>(p);

为什么只使用 reinterpret_cast 而不调用 U_ALIASING_BARRIER 会不安全?

最佳答案

猜测是为了阻止任何违反 strict aliasing rule 的行为,这可能发生在调用尚未完全清理的代码中,从而导致优化时出现意外行为(对此的提示在上面的评论中:“指针的障碍甚至跨函数边界的抗锯齿优化。”)。

严格的别名规则禁止在具有不兼容类型时取消引用具有相同值的别名的指针(C 概念,但 C++ 用更多的词表示类似的事情)。这里有一个小问题:char16_tuint16_t 不需要兼容。 uint16_t 实际上是一个可选支持的类型(在 C 和 C++ 中); char16_t 等价于 uint_least16_t ,它不一定是相同的类型。它在 x86 上将具有相同的宽度,但编译器不需要将它标记为实际上是同一件事。假设通常表示不同意图的类型可以别名,它甚至可能是故意松懈的。

链接的答案中有更完整的解释,但基本上给出了这样的代码:

uint16_t buffer[] = ...

buffer[0] = u'a';
uint16_t * pc1 = buffer;

char16_t * pc2 = (char16_t *)pc1;
pc2[0] = u'b';

uint16_t c3 = pc1[0];

...如果由于某种原因编译器没有将 char16_tuint16_t 标记为兼容,并且您正在编译优化以包含其等效的 -fstrict-aliasing ,则可以假设通过 pc2 写入无法修改任何 pc1指向,而不是在将值分配给 c3 之前重新加载值,可能改为给它 u'a'

有点像示例的代码可能会在转换过程中途出现,其中以前的代码很高兴在任何地方使用 uint16_t *,但现在在块的顶部提供了 char16_t * 以与 ICU 59 兼容,在下面的所有代码之前已完全更改为仅通过正确键入的指针读取。

由于编译器通常不会优化手工编码的程序集,asm 块的存在将迫使它检查所有关于寄存器状态和其他临时值的假设,并在第一次取消引用时完全重新加载每个值在 U_ALIASING_BARRIER 之后,无论优化标志如何。如果您继续通过转换下方的 uint16_t * 编写,这将不会保护您免受任何进一步的别名问题(如果您这样做,这在合法地是您自己的错),但它至少应该确保转换调用之前的状态没有“t 以可能导致通过新指针写入之后被意外跳过的方式持续存在。

关于icu - 为什么 ICU 在进行 reinterpret_cast 时使用这个别名屏障?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/46289805/

10-12 14:12