更严格对齐类型的 VLD2 结构负载

VLD2 structure load of a stricter alignment type

可以将字节指针安全地传递给 vld2q_u16 吗? 我最关心静态分析器的投诉。

uint16x8x2_t load_interleaved_shorts (const uint8_t* const ptr) {
    uint16_t* p16 = (uint16_t*)ptr; // possible undefined behavior ?
    return vld2q_u16(p16);
}

以我为例: 指针始终与 16 字节边界对齐。 编译器不知道指针的对齐方式。 代码必须可移植,严格遵循C90标准。

假设: 将 vld2q_u16 替换为 vld1q_u8 / vuzpq_u8 会损害性能。 编译器将标量模式优化为 vld2q_u16 的概率很小。

编辑:通过转换为空指针来抑制一些警告。 vld2q_u16((const uint16_t*)(const void*)src)

The code must be portable and strictly follow the C90 standard.

...加上 ARM NEON 内在函数的存在所暗示的一切! (尽管这可能对静态分析器没有帮助)。 (相关: 讨论了 x86,但你的情况有点不同;你的指针是对齐的)。


在 C 中,在指针类型之间进行转换(不取消引用)是安全的,只要您从不创建与其类型对齐不足的指针。您不需要编译时可见的对齐保证,您只需要永远不要创建没有 alignof(uint16_t) 对齐的 uint16_t*

(这使得静态分析器不太可能抱怨,即使情况并非如此,除非它可以看到类似 (uint16_t*)(1 + (char*)&something_aligned) 的内容,其中您采用对齐地址并将其偏移奇数,这肯定会产生一个未对齐的地址。)

实际上,针对字节可寻址机器的编译器或多或少地定义了行为,即使是创建未对齐的指针也是如此。 (例如,用于未对齐加载的 Intel 内在函数依赖于创建未对齐的 __m128i*。)只要您不取消引用它们,即使在允许未对齐加载的目标上实践中也是不安全的;请参阅 和涵盖其他示例的博客链接。

所以你 100% 没问题:你的代码永远不会创建未对齐的 uint16_t*,也不会直接取消引用它。

如果 ARM 具有未对齐加载内在函数,则形成未对齐 uint16_t* 并将其传递给函数甚至是安全的;内在函数 API 的 existence/design 意味着以这种方式使用它是安全的。


其他未定义行为但您没有做的事情:

  • 从技术上讲,形成一个不指向对象内部的指针或尾数是 UB,但实际上主流实现也允许这样做。

  • 取消引用不指向 uint16_t 对象的 uint16_t* 是严格别名 UB。但是任何解除引用只发生在内部“函数”中,所以你不必担心严格的别名规则。 (这可能会指针转换为某些特殊类型并取消引用,或者可能会将指针传递给内置的 __builtin_arm_whatever() 编译器。)

我假设 ARM load/store 内部函数的定义类似于 memcpy,能够 read/write 任何对象的字节。 所以例如您可以在 intdoublechar 的数组上 vld2q_u16。英特尔内在函数 以这种方式定义的(例如 GCC/clang 使用 __attribute__((may_alias))。)如果不是这样,那就不安全了。

顺便说一句,char*-can-alias-anything 规则只能以一种方式起作用。是的,将 char* 指向 uint16_t 是安全的,但是如果你有一个 char buf[100] 的实际 array,那些对象肯定是 char 对象,通过 uint16_t* 访问它们是 UB。但是,如果您只有 char*,并且只使用了 char* 以外的其他指针类型,那么您可以将内存视为具有其他类型的内存,并且每个 char* 访问别名。