为什么 ICU 在执行 reinterpret_cast 时使用此别名屏障?

Why does the ICU use this aliasing barrier when doing a reinterpret_cast?

我正在将代码从 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 的行为,这些行为可能发生在调用尚未完全清理的代码中,从而在优化时导致意外行为(对此的提示是在上面的评论中:"Barrier for pointer anti-aliasing optimizations even across function boundaries.").

严格的别名规则禁止取消引用在具有不兼容类型时别名相同值的指针(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 之后第一次取消引用时重新加载每个值,无论优化标志如何。如果您继续 write 通过转换下方的 uint16_t * ,这不会保护您免受任何进一步的别名问题的影响(如果您这样做,那完全是您自己的错),但是它至少应确保转换调用之前的状态不会以可能导致之后意外跳过通过新指针进行写入的方式持续存在。