Преобразование буфера uint8_t* в uint16_t и изменение порядка следования байтов

Я хочу обработать данные, предоставленные внешней библиотекой.

Библиотека хранит данные и предоставляет к ним доступ следующим образом:

const uint8_t* data;
std::pair<const uint8_t*, const uint8_t*> getvalue() const {
  return std::make_pair(data + offset, data + length);
}

Я знаю, что текущие данные содержат два числа uint16_t, но мне нужно изменить их порядок байтов. Таким образом, данные имеют длину 4 байта и содержат следующие числа:

66 4 0 0

Итак, я хотел бы получить два числа uint16_t со значением 1090 и 0 соответственно.

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

pair<const uint8_t*, const uint8_t*> dataPtrs = library.value();
vector<uint8_t> data(dataPtrs.first, dataPtrs.second);

uint16_t first = data[1] <<8 + data[0]
uint16_t second = data[3]<<8 + data[2]

Однако я хотел бы сделать что-то более элегантное (вектор можно заменить, если есть лучший способ получить uint16_ts).

Как мне лучше создать uint16_t из uint8_t*? Я бы по возможности избегал memcpy и использовал что-то более современное/безопасное.

В Boost есть хорошая библиотека с порядком байтов , который может работать, но для него требуется ввод uint16_t.

Чтобы пойти дальше, Boost также предоставляет типы данных для изменения порядка следования байтов, поэтому я мог бы создать структуру:

struct datatype {
    big_int16_buf_t     data1;
    big_int16_buf_t     data2;
}

Можно ли безопасно (отступы, зависимость от платформы и т. д.) преобразовать действительное 4-байтовое число uint8_t* в datatype? Может быть, с чем-то вроде этого союза?

typedef union {
    uint8_t u8[4];
    datatype correct_data;
} mydata;

person Daniel    schedule 11.06.2020    source источник
comment
Переинтерпретация приведения через объединение не допускается в C++ (кроме как между соответствующими членами структур стандартного макета). Это только Cism. Вам нужно будет memcpy() или из С++ 20 std::bit_cast().   -  person underscore_d    schedule 11.06.2020
comment
@underscore_d Вы не можете std::bit_cast из указателя (если только вы не используете значение указателя, а не заостренные объекты).   -  person eerorika    schedule 11.06.2020
comment
@eerorika Ну, да, но вы можете удалить указатель, чтобы получить байты, а затем выполнить битовое приведение этих байтов к нужному типу. Я исходил из последнего фрагмента кода, который использует значения.   -  person underscore_d    schedule 11.06.2020
comment
@underscore_d Если вы косвенно используете std::uint8_t*, то вы получаете std::uint8_t, размер которого не соответствует размеру бит_преобразования в std::uint16_t (или в datatype). Я не понимаю, как здесь можно использовать bit_cast.   -  person eerorika    schedule 11.06.2020


Ответы (2)


Может быть, с чем-то вроде этого союза?

Нет. Игра слов с союзами плохо определена в C++.

Это будет работать, если предположить, что big_int16_buf_t и, следовательно, datatype тривиально копируется:

datatype d{};
std::memcpy(&d, data, sizeof d);
uint16_t first = data[1] <<8 + data[0]
uint16_t second = data[3]<<8 + data[2]

Однако я хотел бы сделать что-то более элегантное

Это на самом деле (субъективно, на мой взгляд) довольно элегантный способ, потому что он работает одинаково на всех системах. Это считывает данные с обратным порядком байтов, независимо от того, является ли ЦП маленьким, большим или каким-либо другим порядком байтов. Это хорошо портативно.

Однако я хотел бы сделать что-то более элегантное (вектор можно заменить, если есть лучший способ получить uint16_ts).

Вектор кажется совершенно бессмысленным. Вы также можете использовать:

const std::uint8_t* data = dataPtrs.first;
person eerorika    schedule 11.06.2020
comment
Является ли подход memcpy хуже с точки зрения переносимости по сравнению с ручным побитовым методом? - person Daniel; 11.06.2020
comment
@Daniel Memcopy в uint16_t было бы нехорошо, потому что результат будет зависеть от исходного порядка следования байтов. Я не знаю, как точно работает big_int16_buf_t, поэтому я не уверен, что memcpy в него делает то, что хотелось бы. Это может быть просто прекрасно. - person eerorika; 11.06.2020

Как мне лучше создать uint16_t из uint8_t*?

Если вы уверены, что данные, стоящие за указателем uint8_t, действительно являются uint16_t, C++ позволяет: auto u16 = *static_cast<uint16_t const*>(data); В противном случае это UB.

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


Но будьте осторожны: если указатель, который вы держите, указывает на два отдельных значения uint8_t, вы не должны преобразовывать их с помощью приведения указателя. В этом случае вам придется вручную указать, какое значение куда следует поместить (возможно, с помощью шаблона функции). Это будет наиболее переносимое решение, и, по всей вероятности, компилятор создаст эффективный код из перестановок и т.д.

person bitmask    schedule 11.06.2020