在 C 中实现 std::bit_cast 等价物

Implementing std::bit_cast equivalent in C

是否可以在 C 中实现类似于 C++20 的 std::bit_cast 的东西?这比使用 union 或将指针转换为不同类型并取消引用要方便得多。

如果你有 bit_cast,那么实现一些浮点函数会更容易:

float Q_rsqrt( float number )
{
    int i = 0x5f3759df - ( bit_cast(int, number) >> 1 );
    
    float y = bit_cast(float, i);
    y = y * ( 1.5f - ( number * 0.5f * y * y ) );
    y = y * ( 1.5f - ( number * 0.5f * y * y ) );
    
    return y;
}

另见 Fast inverse square root

天真的解决方案是:

#define bit_cast(T, ...) (*(T*) &(__VA_ARGS__))

但它有主要问题:

  1. 这是未定义的行为,因为它违反了严格的别名
  2. 它不适用于位转换右值,因为我们直接获取第二个操作数的地址
  3. 它不能确保操作数具有相同的大小

我们可以实现没有这些问题的 bit_cast 吗?

在非标准标准 C 中是可能的,感谢 typeoftypeof is also a further proposed feature for C23,所以在标准C23中可能成为可能。下面的解决方案之一做出了一些牺牲,以允许符合 C99。

使用union

实现

让我们先看看使用 union 的方法是如何工作的:

#define bit_cast(T, ...) \
  ((union{typeof(T) a; typeof(__VA_ARGS__) b;}) {.b=(__VA_ARGS__)}.a)

我们正在从由 T 和给定表达式具有的任何类型组成的匿名联合创建复合文字。我们使用指定的初始化器将此文字初始化为 .b= ...,然后访问类型为 T.

.a 成员

由于 C 的类型语法,如果我们想双关函数指针、数组等,typeof(T) 是必需的。

使用memcpy

实现

这个实现稍长一些,但具有仅依赖 C99 的优点,甚至可以在不使用 typeof:

的情况下工作
#define bit_cast(T, ...) \
    (*(typeof(T)*) memcpy(&(T){0}, &(typeof(__VA_ARGS__)) {(__VA_ARGS__)}, sizeof(T)))

我们正在从一个复合文字复制到另一个复合文字,然后访问目标值:

  • 源文字是我们输入表达式的一个副本,它允许我们获取它的地址,即使对于 bit_cast(float, 123),其中 123 是一个右值
  • 目标是 T
  • 类型的零初始化文字

memcpy returns 目标操作数,因此我们可以将结果转换为 typeof(T)* 然后取消引用该指针。

我们可以在这里完全消除typeof并使这个C99兼容,但也有缺点:

#define bit_cast(T, ...) \
    (*((T*) memcpy(&(T){0}, &(__VA_ARGS__), sizeof(T))))

我们现在直接获取表达式的地址,所以我们不能再对右值使用 bit_cast。我们正在使用 T* 而没有 typeof,所以我们不能再转换为函数指针、数组等

实施大小检查(自 C11 起)

至于最后一个问题,即我们不验证两个操作数是否具有相同的大小:我们可以使用 _Static_assert(自 C11 起)来确保这一点。不幸的是,_Static_assert 是一个声明,而不是一个表达式,所以我们必须把它包起来:

#define static_assert_expr(...) \
    ((void) (struct{_Static_assert(__VA_ARGS__); int _;}) {0})

我们正在创建一个包含断言并丢弃表达式的复合文字。

我们可以使用逗号运算符轻松地将其集成到前两个实现中:

#define bit_cast_memcpy(T, ...) ( \
    static_assert_expr(sizeof(T) == sizeof(__VA_ARGS__), "operands must have the same size"), \
    (*(typeof(T)*) memcpy(&(T){0}, &(typeof(__VA_ARGS__)) {(__VA_ARGS__)}, sizeof(T))) \
)

#define bit_cast_union(T, ...) ( \
    static_assert_expr(sizeof(T) == sizeof(__VA_ARGS__), "operands must have the same size"), \
    ((union{typeof(T) a; typeof(__VA_ARGS__) b;}) {.b=(__VA_ARGS__)}.a) \
)

已知和无法解决的问题

由于宏的工作原理,如果双关类型包含逗号,我们将无法使用它:

bit_cast(int[0,1], x)

这不起作用,因为宏会忽略方括号并且 1] 不会被视为类型的一部分,但会进入 __VA_ARGS__.