Чтение двойного порядка байтов с помощью объединения и сдвига битов, это безопасно? - PullRequest
0 голосов
/ 03 октября 2018

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

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

Это заставило меня задуматься о том, что можно было бы использовать объединение иТехнология битового сдвига для чтения двойных (и плавающих) значений из буферов, и реализация быстрого теста, похоже, сработала (по крайней мере, с помощью clang на x86_64):

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

double read_double(char * buffer, bool le) {
    union {
        double d;
        uint64_t i;
    } data;
    data.i = 0;

    int off = le ? 0 : 7;
    int add = le ? 1 : -1;
    for (int i = 0; i < 8; i++) {
        data.i |= ((uint64_t)(buffer[off] & 0xFF) << (i * 8));
        off += add;
    }
    return data.d;
}

int main() {
    char buffer_le[] = {0x6E, 0x86, 0x1B, 0xF0, 0xF9, 0x21, 0x09, 0x40};
    printf("%f\n", read_double(buffer_le, true)); // 3.141590

    char buffer_be[] = {0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E};
    printf("%f\n", read_double(buffer_be, false)); // 3.141590

    return 0;
}

Мой вопрос, хотя, является ли это безопасным способом сделатьэтот?Или здесь присутствует неопределенное поведение?Или если и этот, и метод перестановки байтов связаны с неопределенным поведением, один из них безопаснее другого?

Ответы [ 2 ]

0 голосов
/ 03 октября 2018

Чтение двойного порядка байтов для платформы с объединением и сдвигом битов, это безопасно?

Подобные вещи имеют смысл только при работе с данными извне программы (например, данными изфайл или сеть);где у вас есть строгий формат для данных (определенный в спецификации формата файла или спецификации сетевого протокола), который может не иметь никакого отношения к использованию формата C, может не иметь ничего общего с использованием процессора и может не иметь формат IEEE 754либо.

С другой стороны, С не дает никаких гарантий вообще.В качестве простого примера вполне допустимо, чтобы компилятор использовал формат BCD для float, где 0x12345e78 = 1.2345 * 10**78, даже если сам процессор поддерживает IEEE 754.

Результатесть ли у вас «какой бы формат ни указывал в спецификации» извне программы, и вы конвертируете это в другой «какой бы формат не ощущался компилятором» для использования внутри программы;и каждое сделанное вами предположение (включая sizeof(double)) потенциально неверно.

0 голосов
/ 03 октября 2018

Переинтерпретация через объединение

Построение значения uint64_t с помощью сдвига и байтов ORing, конечно, поддерживается стандартом C.(Существует некоторая опасность при сдвиге из-за необходимости убедиться, что левый операнд имеет правильный размер и тип, чтобы избежать проблем с переполнением и шириной сдвига, но код в вопросе правильно преобразуется в uint64_t перед сдвигом.) Тогда вопросоставшийся для кода вопрос о том, разрешена ли реинтерпретация через объединение стандартом C.Ответ: да.

C 6.5.2.3 3 говорит:

Постфиксное выражение, за которым следует оператор . , а идентификатор обозначает член структурыили объект объединения.Значение соответствует названному элементу, 99)

, а примечание 99 гласит:

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

Такая реинтерпретация, конечно, основана на представлениях объектов, используемых в реализации на языке Си.В частности, double должен использовать ожидаемый формат, соответствующий байту, считанному из входного потока.

Изменение байтов объекта

Изменение объекта путем изменения его байтов (как с помощьюуказатель на unsigned char) разрешен C. C 2018 6.5 7 говорит:

Доступ к сохраненному значению объекта должен быть доступен только через выражение lvalue, которое имеет один из следующих типов: [списокразличные типы] или символьный тип.

Хотя в одном из комментариев говорится, что вы можете «обращаться», но не «изменять» байты объекта таким образом (очевидно, интерпретируя «доступ» к значениютолько чтение, а не запись), C 2018 3.1 определяет «доступ» как:

для чтения или изменения значения объекта.

Таким образом, разрешено:читать или записывать байты объекта через символьные типы.

...