То, как я обычно это делаю, выглядит так:
#include <arpa/inet.h> // for ntohs() etc.
#include <stdint.h>
class be_uint16_t {
public:
be_uint16_t() : be_val_(0) {
}
// Transparently cast from uint16_t
be_uint16_t(const uint16_t &val) : be_val_(htons(val)) {
}
// Transparently cast to uint16_t
operator uint16_t() const {
return ntohs(be_val_);
}
private:
uint16_t be_val_;
} __attribute__((packed));
Аналогично для be_uint32_t
.
Тогда вы можете определить свою структуру следующим образом:
struct be_fixed64_t {
be_uint32_t int_part;
be_uint32_t frac_part;
} __attribute__((packed));
Дело в том, что компилятор почти наверняка выложит поля в том порядке, в котором вы их пишете, поэтому все, что вас действительно беспокоит, это целые числа с прямым порядком байтов.Объект be_uint16_t
- это класс, который знает, как прозрачно преобразовывать себя между старшим и машинным порядком при необходимости.Например:
be_uint16_t x = 12;
x = x + 1; // Yes, this actually works
write(fd, &x, sizeof(x)); // writes 13 to file in big-endian form
Фактически, если вы скомпилируете этот фрагмент с помощью какого-либо достаточно хорошего компилятора C ++, вы должны обнаружить, что он выдает "13" с прямым порядком байтов как константу.
СВ этих объектах представление в памяти имеет тип big-endian.Таким образом, вы можете создавать их массивы, размещать их в структурах и т. Д. Но когда вы собираетесь оперировать ими, они волшебным образом приводятся к машинному порядку.Обычно это отдельная инструкция для x86, поэтому она очень эффективна.Существует несколько контекстов, в которых вам нужно выполнить приведение вручную:
be_uint16_t x = 37;
printf("x == %u\n", (unsigned)x); // Fails to compile without the cast
... но для большей части кода вы можете просто использовать их, как если бы они были встроенными типами.