为什么ICU在进行reinterpret_cast时会使用此别名障碍?

时间:2017-09-19 00:03:30

标签: icu 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);

为什么在不调用U_ALIASING_BARRIER的情况下使用reinterpret_cast是不安全的?

1 个答案:

答案 0 :(得分:6)

猜测是,停止在调用尚未完全清理的代码时可能发生的任何违反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 * (如果您这样做,这是合法的自己的错误),这将无法保护您免受任何进一步的别名问题,但它应该在至少确保转换调用之前的状态不会以一种可能导致之后意外跳过新指针的写入的方式持续存在。