Портативная переинтерпретация данных

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

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

  1. Это не переносимо — это нарушает строгое правило алиасинга.

    /* #1 Type Punning */
    
    float float_value = 3.14;
    int *int_pointer = (int *)&float_value;
    int int_value = *int_pointer;
    
  2. Это зависит от платформы, потому что он считывает значение 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;
    
  3. Должно быть хорошо, пока 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. Знаете ли вы другие способы интерпретировать данные переносимо?

person Johannes    schedule 14.12.2011    source источник
comment
$float_value должно быть &float_value ?   -  person wildplasser    schedule 15.12.2011
comment
Повторная интерпретация данных дает результаты, зависящие от платформы. Как это может работать переносимо? Например, разные платформы могут по-разному представлять float в памяти.   -  person Magnus Hoff    schedule 15.12.2011
comment
@MagnusHoff это правда, но все, что мне нужно, это правильный ansi c99 и определенное значение   -  person Johannes    schedule 15.12.2011
comment
@DanFego я прочитал байт-код для виртуальной машины, и мне нужно придать значение массиву байтов   -  person Johannes    schedule 15.12.2011
comment
@Johannes: и определенное значение? Но значение не определено именно из-за того, что я сказал. Или я, возможно, не совсем правильно вас понял?   -  person Magnus Hoff    schedule 15.12.2011
comment
@MagnusHoff версия memcpy всегда должна давать один и тот же результат на одной и той же машине, верно? Насколько я знаю, это не так для № 1 и № 2!   -  person Johannes    schedule 15.12.2011
comment
Также после объединения отсутствует точка с запятой.   -  person wildplasser    schedule 15.12.2011
comment
Насколько я понимаю, # 2 должен был быть определен реализацией (не неопределенным) в C99. Это начиная с TC3 (кто-нибудь может это подтвердить?). Это реализация определена в C1x.   -  person ninjalj    schedule 15.12.2011
comment
@ninjalj, если это правда, это подойдет для моей проблемы   -  person Johannes    schedule 15.12.2011
comment
@Cristoph упоминает сноску об этом в TC3 в комментарии к stackoverflow.com/questions/6486807/ , который, я думаю, соответствует отчету о дефектах open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm   -  person ninjalj    schedule 15.12.2011
comment
@ninjalj: добавил сноску в качестве ответа...   -  person Christoph    schedule 15.12.2011
comment
В этой статье обсуждается именно эта проблема: blog.regehr.org/archives/959.   -  person Tor Klingberg    schedule 16.01.2015


Ответы (5)


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

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

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

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

to

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

Это не так уж важно, так как приложение носит только информационный, а не нормативный характер.

Имейте в виду, что вы все равно можете получить неопределенное поведение, например

  • путем создания представления-ловушки
  • нарушая правила псевдонимов в случае членов с типом указателя (который в любом случае не должен преобразовываться с помощью каламбура, поскольку не требуется единообразного представления указателя)
  • если члены объединения имеют разные размеры - заданное значение имеют только байты последнего использовавшегося в хранилище члена; в частности, сохранение значений в меньшем элементе также может сделать недействительными замыкающие байты большего элемента.
  • если член содержит байты заполнения, которые всегда принимают неуказанные значения
person Christoph    schedule 15.12.2011
comment
Означает ли фиксированный абзац из Приложения J (Значения байтов, которые соответствуют членам объединения, отличным от того, который последний раз был сохранен в [не указано]), что байты, которые находятся за пределами байтов в объектном представлении записанного члена, имеют неопределенное значение ? Потому что это именно то, что говорится в параграфе 7 6.2.6.1 (open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf). Спасибо. - person user42768; 14.09.2018

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

  2. Можно привести указатель к указателю на (signed/unsigned/) char, поэтому

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

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

person AProgrammer    schedule 14.12.2011
comment
это правда, но это было бы переосмыслением как bytearray - и мне нужен int - person Johannes; 15.12.2011
comment
Это UB в C++, за исключением особого случая POD-структур If a POD-union contains several POD-structs that share a common initial sequence (9.2), and if an object of this POD-union type contains one of the POD-structs, it is permitted to inspect the common initial sequence of any of POD-struct members - person Dave Rager; 15.12.2011
comment
@ Дэйв, да, я не упомянул хак X, потому что он не дает возможности наказывать тип. - person AProgrammer; 15.12.2011

чтобы быть в безопасности, я бы использовал массив байтов (беззнаковый символ), а не «int» для хранения значения.

person Keith Nicholas    schedule 14.12.2011
comment
Тогда возникает другой вопрос: как переинтерпретировать int (или произвольный другой тип) как unsigned char []? - person ; 15.12.2011
comment
@Keith bytearray будет интерпретировать данные как последовательность байтов. что мне нужно, это переосмысление - person Johannes; 15.12.2011
comment
чтобы убедиться, что число с плавающей запятой представлено полностью, т.е. если sizeof(float) != sizeof(int) - person Keith Nicholas; 15.12.2011

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

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

person AndersK    schedule 14.12.2011
comment
спасибо за ваш ответ - я думаю, я не понял, что все, что мне нужно, это правильный ansi c99 и определенное значение - person Johannes; 15.12.2011

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

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

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

person Chris Dodd    schedule 14.12.2011
comment
насколько мне известно, приведение from к char* также нарушает правило - person Johannes; 15.12.2011
comment
это все еще UB - эффективный тип является свойством объекта (т.е. блока памяти), а не указателем, используемым для доступа к нему. - person Christoph; 15.12.2011