更严格对齐类型的 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 任何对象的字节。 所以例如您可以在 int
、double
或 char
的数组上 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*
访问别名。
可以将字节指针安全地传递给 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 内在函数的存在所暗示的一切! (尽管这可能对静态分析器没有帮助)。 (相关:
在 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 任何对象的字节。 所以例如您可以在 int
、double
或 char
的数组上 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*
访问别名。