Перенос данных данных

Я хочу переинтерпретировать данные одного типа как другого типа переносимым способом (C99). Я не говорю о кастинге, я хочу переосмыслить некоторые данные. Кроме того, переносимым я имею в виду, что он не нарушает правила C99 - я не имею в виду, что переопределенное значение равно для всех систем.

Я знаю 3 разных способа переинтерпретировать данные, но только два из них переносимы:

  • Это не переносимо - это нарушает правило строгого сглаживания.

    /* #1 Type Punning */
    
    float float_value = 3.14;
    int *int_pointer = (int *)&float_value;
    int int_value = *int_pointer;
    
  • Это зависит от платформы, потому что он считывает значение int из объединения после записи float в него. Но он не нарушает правил C99, поэтому он должен работать (если sizeof(int) == sizeof(float)).

    /* #2 Union Punning */
    
    union data {
      float float_value;
      int int_value;
    };
    
    union data data_value;
    data_value.float_value = 3.14;
    int int_value = data_value.int_value;
    
  • Должно быть хорошо, если sizeof(int) == sizeof(float)

    /* #3 Copying */
    
    float float_value = 3.14;
    int int_value = 0;
    memcpy(&int_value, &float_value, sizeof(int_value));
    

Мои вопросы:

  • Правильно ли это?
  • Знаете ли вы другие способы переинтерпретации данных переносимым образом?

Ответы

Ответ 1

Решение 2 является переносимым - произвольное использование пулов через союзы всегда было законным в C99, и оно было четко изложено в TC3, в котором добавлена ​​следующая сноска к разделу 6.5.2.3:

Если элемент, используемый для доступа к содержимому объекта объединения, не является тот же самый, что и последний элемент, используемый для хранения значения в объекте, Соответствующая часть объектного представления значения равна реинтерпретируется как представление объекта в новом типе, как описано в 6.2.6 (процесс, иногда называемый "пингом типа" ). Это может быть ловушки.

Приложение J по-прежнему перечисляет его как непоказанное поведение, которое является известным дефектом и было исправлено с помощью C11, который изменил

Значение члена объединения, отличного от последнего, сохраненного в [не указывается]

к

Значения байтов, которые соответствуют членам объединения, отличным от последнего сохраненного в [не указаны]

Это не такая уж большая сделка, поскольку приложение является только информативным, а не нормативным.

Помните, что вы по-прежнему можете иметь поведение undefined, например

  • создав представление ловушки
  • нарушая правила сглаживания в случае членов с типом указателя (который не должен быть преобразован с помощью произвольного ввода типа, так как не должно быть равномерного представления указателя)
  • если члены профсоюза имеют разные размеры - только байты последнего элемента, используемые в магазине, имеют указанное значение; в частности, сохранение значений в меньшем члене также может привести к недействительности конечных байтов более крупного элемента
  • если элемент содержит байты заполнения, которые всегда принимают неопределенные значения

Ответ 2

  • Решение объединения определено как memcpy в C (AFAIK, это UB в С++), см. DR283

  • Можно указать указатель на указатель на (signed/unsigned/) char, поэтому

    unsigned char *ptr = (unsigned char*)&floatVar;
    

    а затем доступ к ptr [0] в ptr [sizeof (floatVar) -1] является законным.

Ответ 3

чтобы быть в безопасности, я бы пошел с байтовым массивом (unsigned char), а не с 'int', чтобы удерживать значение.

Ответ 4

тип данных int является примером непереносимого типа, так как endianness может изменять порядок байтов между платформами.

если вы хотите быть портативным, вам нужно определить свои собственные типы, а затем реализовать их на каждой платформе, к которой вы хотите подключиться. Затем определите методы преобразования для ваших типов данных. Это, насколько я знаю, единственный способ иметь полный контроль над байтовыми заказами и т.д.

Ответ 5

Если вы хотите избежать правила строгого сглаживания, вам нужно сначала направить указатель char:

float float_value = 3.14;
int *int_pointer = (int *)(char *)&float_value;
int int_value = *int_pointer;

Обратите внимание, что у вас может быть sizeof(int) > sizeof(float), и в этом случае вы по-прежнему получаете поведение undefined