使用`union`在整数和数组之间输入双关语?
Type punning between integer and array using `union`?
在整数和整数数组之间进行类型双关是否合法?
具体代码:
#include <nmmintrin.h>
#include <stdint.h>
union Uint128 {
__uint128_t uu128;
uint64_t uu64[2];
};
static inline uint_fast8_t popcnt_u128 (__uint128_t n)
{
const union Uint128 n_u = {.uu128 = n};
const uint_fast8_t cnt_a = _mm_popcnt_u64(n_u.uu64[0]);
const uint_fast8_t cnt_b = _mm_popcnt_u64(n_u.uu64[1]);
const uint_fast8_t cnt = cnt_a + cnt_b;
return cnt;
}
是的,C 标准明确预见了通过联合在所有数据类型之间进行类型双关。没有针对数组的特殊规定禁止这样做。
是的,联合类型双关在 ISO C99 及更高版本中是合法的。 Unions and type-punning
还有 Is type-punning through a union unspecified in C99, and has it become specified in C11?(在 C89 中,它是 实现定义,而不是 未定义)。
作为 GNU 扩展,它在 gnu89 和 GNU C++ 中有明确的定义。 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning
它在 MSVC++ 中也是合法的,例如使用联合来定义 __m128i
以访问向量元素。 (并且还允许指针转换以进行类型双关,这与其他强制执行严格别名的编译器不同。)
请注意,在 ISO C++ 中 不 合法读取除最后写入的联合成员之外的成员(未定义行为) .这是一个常见的扩展,我认为所有 x86 编译器都支持(并且您使用的是英特尔内部函数),但并非所有地方的所有编译器都应该被假定为。
对于严格可移植的 C++,您始终可以使用 memcpy
在两种不同类型的对象表示之间进行复制。
对于您的情况,任何体面的优化编译器应该编译它与(uint64_t)n
和n>>64
相同,除非您禁用优化。
C11 草案 N1570 第 6.5 节 C 段中的类型访问规则没有规定 struct
或 union
类型的对象可以通过左值以外的任何其他方式访问其存储union
类型,包含此类 union
的另一种类型的左值,或字符类型的左值。
能够看到一种类型的指针或左值被用于派生随后访问的另一种指针或左值的高质量编译器应该能够识别在上下文中对后者的访问在推导可见的地方,是对前者的访问。我认为该标准的作者认为它足够明显,可以不言而喻,特别是因为即使像 someUnion.intMember = 3;
这样的东西也会以其他方式调用 UB。赋值的左侧操作数是 int
类型的左值,并且没有规定允许使用 int
类型的左值来访问联合类型的对象。编译器识别通过派生指针或左值访问是对父级的访问的情况范围是实现质量问题;该标准没有提供关于 "good" 实施的预期结果的指导。
至于 clang 和 gcc 允许什么,他们似乎承认对 someUnion.someArray[i]
的访问是对联合的访问,但他们不 承认 *(someUnion.someArray+i)
同样,即使标准将这两个构造定义为等效的。由于标准不 要求 实现可以识别(甚至是明显的 someUnion.intMember
),这种差异不会使 clang 和 gcc 不符合要求。尽管如此,应该指出的是,在识别基于联合的左值时,他们是惊人的盲目。
在整数和整数数组之间进行类型双关是否合法?
具体代码:
#include <nmmintrin.h>
#include <stdint.h>
union Uint128 {
__uint128_t uu128;
uint64_t uu64[2];
};
static inline uint_fast8_t popcnt_u128 (__uint128_t n)
{
const union Uint128 n_u = {.uu128 = n};
const uint_fast8_t cnt_a = _mm_popcnt_u64(n_u.uu64[0]);
const uint_fast8_t cnt_b = _mm_popcnt_u64(n_u.uu64[1]);
const uint_fast8_t cnt = cnt_a + cnt_b;
return cnt;
}
是的,C 标准明确预见了通过联合在所有数据类型之间进行类型双关。没有针对数组的特殊规定禁止这样做。
是的,联合类型双关在 ISO C99 及更高版本中是合法的。 Unions and type-punning 还有 Is type-punning through a union unspecified in C99, and has it become specified in C11?(在 C89 中,它是 实现定义,而不是 未定义)。
作为 GNU 扩展,它在 gnu89 和 GNU C++ 中有明确的定义。 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning
它在 MSVC++ 中也是合法的,例如使用联合来定义 __m128i
以访问向量元素。 (并且还允许指针转换以进行类型双关,这与其他强制执行严格别名的编译器不同。)
请注意,在 ISO C++ 中 不 合法读取除最后写入的联合成员之外的成员(未定义行为) .这是一个常见的扩展,我认为所有 x86 编译器都支持(并且您使用的是英特尔内部函数),但并非所有地方的所有编译器都应该被假定为。
对于严格可移植的 C++,您始终可以使用 memcpy
在两种不同类型的对象表示之间进行复制。
对于您的情况,任何体面的优化编译器应该编译它与(uint64_t)n
和n>>64
相同,除非您禁用优化。
C11 草案 N1570 第 6.5 节 C 段中的类型访问规则没有规定 struct
或 union
类型的对象可以通过左值以外的任何其他方式访问其存储union
类型,包含此类 union
的另一种类型的左值,或字符类型的左值。
能够看到一种类型的指针或左值被用于派生随后访问的另一种指针或左值的高质量编译器应该能够识别在上下文中对后者的访问在推导可见的地方,是对前者的访问。我认为该标准的作者认为它足够明显,可以不言而喻,特别是因为即使像 someUnion.intMember = 3;
这样的东西也会以其他方式调用 UB。赋值的左侧操作数是 int
类型的左值,并且没有规定允许使用 int
类型的左值来访问联合类型的对象。编译器识别通过派生指针或左值访问是对父级的访问的情况范围是实现质量问题;该标准没有提供关于 "good" 实施的预期结果的指导。
至于 clang 和 gcc 允许什么,他们似乎承认对 someUnion.someArray[i]
的访问是对联合的访问,但他们不 承认 *(someUnion.someArray+i)
同样,即使标准将这两个构造定义为等效的。由于标准不 要求 实现可以识别(甚至是明显的 someUnion.intMember
),这种差异不会使 clang 和 gcc 不符合要求。尽管如此,应该指出的是,在识别基于联合的左值时,他们是惊人的盲目。