Преобразование типов

Type punning

Последнее обновление: 22.10.2023

Type punning представляет изменение типа переменной без изменения ее представления в памяти. На русском языке нет адекватных переводов, но приблизительно можно перевести как "двусмысленность" или "двойственность" типов/типизации (значение одного типа можно принять за значение другого типа). Например, если необходимо отправлять 32-битные значения float, используя протокол связи, который может отправлять только один байт за раз, то придется сначала необходимо преобразовать число float в тип, которым можно манипулировать побайтно. Обычно это означает, что в программном обеспечении его следует рассматривать как целочисленный тип. Поскольку приведение типов меняет используемое представление, необходимо использовать type punning.

Есть два широко используемых метода type punning: использование указателей и использование объединений (union). В кратце рассмотрим оба метода. Но следует сразу отметить, что применяемые типы должны иметь совместимое выравнивание/размер в памяти.

Применение указателей

Применение указателей заключается в преобразование указателя на исходный объект к указателю на целевой тип с последующим разыменованием. Например, преобразование из набора беззнаковых байтов в 32-разрядное целое число со знаком:

int32_t int32_from_bytes(uint8_t* buffer)
{
  // Преобразуем указатель на байты в указатель на int32 и разыменовываем
  return *((int32_t*) buffer);
}

Рассмотрим на примере:

#include <stdio.h>
#include <stdint.h>

// Преобразуем набор байтов в 32-разрядное целое число со знаком
int32_t int32_from_bytes(uint8_t* buffer)
{
  // Преобразуем указатель на байты в указатель на int32 и разыменовываем
  return *((int32_t*) buffer);
}

//Преобразуем 32-разрядное целое число со знаком в набора байтов
void int32_to_bytes(int32_t value, uint8_t* buffer)
{
  // первый элемент массива байтов - первый (самый младший) байт числа int32
  buffer[0] = (uint8_t)value;
  buffer[1] = (uint8_t)(value >> 8);  // второй байт числа int32
  buffer[2] = (uint8_t)(value >> 16); // третий байт числа int32
  buffer[3] = (uint8_t)(value >> 24); // четвертый байт числа int32
}

int main(void){

  uint32_t number = 0x05040302;
  uint8_t bytes[4];
  // преобразование из int32_t в uint8_t
  int32_to_bytes(number, bytes);
  // для проверки выводим на консоль
  for(int i=0; i< 4; i++){
    printf("bytes[%d]: %d\n", i, bytes[i]);
  }
  
  
  // обратное преобразование из uint8_t в int32_t
  uint32_t val = int32_from_bytes(bytes);
  printf("Restored number: %#x\n", val);

  return 0;
}

В функции int32_to_bytes() преобразуем число типа int32_t в набор байтов - четырех значений uint8_t. Для этого применяем операции сдвига:

void int32_to_bytes(int32_t value, uint8_t* buffer)
{
  // первый элемент массива байтов - первый (самый младший) байт числа int32
  buffer[0] = (uint8_t)value;
  buffer[1] = (uint8_t)(value >> 8);  // второй байт числа int32
  buffer[2] = (uint8_t)(value >> 16); // третий байт числа int32
  buffer[3] = (uint8_t)(value >> 24); // четвертый байт числа int32
}

После этого параметр buffer будет содержать все байты числа value из первого параметра.

В другой функции - int32_from_bytes применяем type punning, обратно преобразуя набор из байтов в число int32_t:

int32_t int32_from_bytes(uint8_t* buffer)
{
  // Преобразуем указатель на байты в указатель на int32 и разыменовываем
  return *((int32_t*) buffer);
}

То есть выражение (int32_t*) buffer получает указатель типа (int32_t*), который затем разыменовывается с помощью выражения *((int32_t*) buffer)

В функции main выполняем преобразование из числа int32_t в набор байтов и обратно. Консольный вывод программы:

c:\C>gcc -Wall -pedantic app.c -o app   & app
bytes[0]: 2
bytes[1]: 3
bytes[2]: 4
bytes[3]: 5
Restored number: 0x5040302

c:\C>

Использование объединений

При использовании объединений нам надо определить union, который содержит значения обоих типов. Например:

union
  {
    uint8_t bytes[4];
    float number;
  } type_swap;

Здесь объединение type_swap имеет размер в 4 байта. При этом на этих 4 байтах может располагаться либо 4 значения uint8_t, либо одно значение float. Рассмотрим на примере:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

// Считываем 32-разрядное целое число со знаком из набора байтов
void float_to_bytes(float value, uint8_t* buffer)
{
  // применяем библиотечную функцию memcpy
   memcpy(buffer, &value, sizeof(float));
}

// получаем значение float из массива байт
float float_from_bytes(uint8_t * buffer)
{
  union
  {
    uint8_t bytes[4];
    float number;
  } type_swap;

  type_swap.bytes[0] = buffer[0];
  type_swap.bytes[1] = buffer[1];
  type_swap.bytes[2] = buffer[2];
  type_swap.bytes[3] = buffer[3];
  return type_swap.number;
}


int main(void){

  float num = 3.1415;
  uint8_t bytes[sizeof(float)];
  // преобразование из float в uint8_t
  float_to_bytes(num, bytes); 
  
  // обратное преобразование из uint8_t во float
  float val = float_from_bytes(bytes);
  printf("Restored number: %f\n", val);

  return 0;
}

Здесь функция float_to_bytes() принимает значение float и набор байтов и с помощью библиотечной функции memcpy копирует байты из float в массив uint8_t

void float_to_bytes(float value, uint8_t* buffer)
{
  // применяем библиотечную функцию memcpy
   memcpy(buffer, &value, sizeof(float));
}

Функция float_from_bytes() выполняет восстановления из массива байт, который передается в качестве параметра, в число float:

float float_from_bytes(uint8_t* buffer)
{
  union
  {
    uint8_t bytes[4];
    float number;
  } type_swap;

  type_swap.bytes[0] = buffer[0];
  type_swap.bytes[1] = buffer[1];
  type_swap.bytes[2] = buffer[2];
  type_swap.bytes[3] = buffer[3];
  return type_swap.number;
}

Для восстановления внутри функции определяем объединение type_swap и побайтно копируем переданные байты в массив bytes. И в конце эти же байты возвращаем как число float. В функции main выполняем тестирование фукций. Консольный вывод программы:

c:\C>gcc -Wall -pedantic app.c -o app   & app
Restored number: 3.141500

c:\C>
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850