Реализация эквивалента std :: bit_cast в C

Можно ли реализовать что-то похожее на C ++ 20? std::bit_castв C? Это было бы намного удобнее, чем использовать union или приведение указателей к разным типам и разыменование.

Если бы у вас был, тогда было бы проще реализовать некоторые функции с плавающей запятой:

      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;
}

См. Также Быстрый обратный квадратный корень

Наивное решение:

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

Но у него есть серьезные проблемы:

  1. это неопределенное поведение, потому что оно нарушает строгий псевдоним
  2. он не работает для rvalue с побитовым преобразованием, потому что мы напрямую берем адрес второго операнда
  3. он не гарантирует, что операнды имеют одинаковый размер

Можем ли мы реализовать bit_cast без этих проблем?

1 ответ

Это возможно в нестандартном стандарте C, благодаря . также является еще одной предлагаемой функцией для C23 , поэтому она может стать возможной в стандарте C23. Одно из приведенных ниже решений приносит некоторые жертвы, которые обеспечивают соответствие C99.

Реализация с использованием

Давайте посмотрим, как подход с использованием unionработает сначала:

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

Мы создаем составной литерал из анонимного объединения, состоящего из любого типа данного выражения. Мы инициализируем этот литерал для .b= ...используя назначенные инициализаторы, а затем получить доступ к .aчлен типа .

The typeof(T)необходимо, если мы хотим каламбурить указатели функций, массивы и т. д. из-за синтаксиса типа C.

Реализация с использованием

Эта реализация немного длиннее, но имеет то преимущество, что она полагается только на C99 и может работать даже без использования :

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

Мы копируем из одного составного литерала в другой, а затем получаем доступ к целевому значению:

  • исходный литерал является копией нашего входного выражения, что позволяет нам брать его адрес даже для bit_cast(float, 123)куда 123является rvalue
  • пункт назначения является инициализированным нулем литералом типа T

memcpyвозвращает операнд назначения, поэтому мы можем привести результат к typeof(T)*а затем разыменовать этот указатель.

Мы можем полностью отказаться от этого и сделать его совместимым с C99, но есть недостатки:

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

Теперь мы берем адрес выражения напрямую, поэтому мы не можем использовать bit_castна rvalues ​​больше. мы используем T*без typeof, поэтому мы больше не можем конвертировать в указатели на функции, массивы и т. д.

Внедрение проверки размера (начиная с C11)

Что касается последней проблемы, заключающейся в том, что мы не проверяем, что оба операнда имеют одинаковый размер: мы можем использовать (начиная с 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__.

Другие вопросы по тегам