Есть ли способ обеспечить конкретный порядок байтов для структуры C или C ++? - PullRequest
25 голосов
/ 18 июля 2011

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

Что бы я хотел сейчас, однако, если есть способ обеспечить конкретный порядок байтов данной структуры . Есть ли хорошие директивы компилятора или другие простые решения, кроме переписывания всего этого из множества макросов, манипулирующих битовыми полями?

Было бы неплохо общее решение, но я бы тоже был рад конкретному решению gcc.

Edit:

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

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

Об изменении программного обеспечения для встроенного процессора не может быть и речи. Источник доступен, поэтому я намерен использовать структуры из этой системы вместо того, чтобы начинать с нуля и оценивать все данные побайтно.

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

Это не должно быть реальным изменением порядка байтов. Даже если это всего лишь интерфейс, и физически все обрабатывается в собственном порядке байтов процессоров, для меня это вполне приемлемо.

Ответы [ 10 ]

20 голосов
/ 18 июля 2011

То, как я обычно это делаю, выглядит так:

#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

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

12 голосов
/ 05 декабря 2016

Немного опоздал на вечеринку, но с текущим GCC (проверено на 6.2.1, где он работает, и 4.9.2, где он не реализован), наконец, есть способ объявить, что структура должна храниться в порядке байтов X-endian.

Следующая тестовая программа:

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

struct __attribute__((packed, scalar_storage_order("big-endian"))) mystruct {
    uint16_t a;
    uint32_t b;
    uint64_t c;
};


int main(int argc, char** argv) {
    struct mystruct bar = {.a = 0xaabb, .b = 0xff0000aa, .c = 0xabcdefaabbccddee};

    FILE *f = fopen("out.bin", "wb");
    size_t written = fwrite(&bar, sizeof(struct mystruct), 1, f);
    fclose(f);
}

создает файл "out.bin", который можно проверить с помощью шестнадцатеричного редактора (например, hexdump -C out.bin).Если атрибут scalar_storage_order поддерживается, он будет содержать ожидаемый 0xaabbff0000aaabcdefaabbccddee в этом порядке и без дырок.К сожалению, это, конечно, очень специфично для компилятора.

3 голосов
/ 18 июля 2011

Нет, я так не думаю.

Endianness - это атрибут процессора, который указывает, представлены ли целые числа слева направо или справа налево, это не атрибут компилятора.

Лучшее, что вы можете сделать, это написать код, который не зависит от порядка байтов.

2 голосов
/ 18 июля 2011

Нет, такой возможности нет.Если он существует, это может привести к тому, что компиляторам придется генерировать чрезмерный / неэффективный код, чтобы C ++ просто не поддерживал его.

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

1 голос
/ 01 октября 2015

Boost предоставляет для этого порядковые буферы .

Например:

#include <boost/endian/buffers.hpp>
#include <boost/static_assert.hpp>

using namespace boost::endian;

struct header {
    big_int32_buf_t     file_code;
    big_int32_buf_t     file_length;
    little_int32_buf_t  version;
    little_int32_buf_t  shape_type;
};
BOOST_STATIC_ASSERT(sizeof(h) == 16U);
1 голос
/ 18 июля 2011

Возможным инновационным решением было бы использование интерпретатора C, такого как Ch, и принудительного кодирования байтов в обратном порядке.

1 голос
/ 18 июля 2011

Я не уверен, что следующее может быть изменено в соответствии с вашими целями, но там, где я работаю, во многих случаях мы обнаружили, что следующее весьма полезно:

Когда важен порядок байтов, мы используем дваразные структуры данных.Одно сделано, чтобы представить, как оно ожидалось.Другой - как мы хотим, чтобы он был представлен в памяти.Затем разрабатываются процедуры преобразования для переключения между ними.

Рабочий процесс работает следующим образом ...

  1. Считывание данных в необработанную структуру.
  2. Преобразование в«Неформатированная структура» в «версии в памяти»
  3. Работа только с «версией в памяти»
  4. Когда закончите работать с ней, преобразуйте «в версии памяти» обратно в «raw»структура "и выпишите это.

Мы находим это разделение полезным, потому что (но не ограничиваясь этим) ...

  1. Все преобразования расположены только в одном месте.
  2. Меньше головной боли из-за проблем с выравниванием памяти при работе с "версией в памяти".
  3. Это значительно упрощает перенос из одной арки в другую (меньше проблем с порядком байтов).

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

0 голосов
/ 18 июля 2011

Для этого существует представление данных, которое называется XDR.Посмотри на это.http://en.wikipedia.org/wiki/External_Data_Representation

Хотя это может быть слишком много для вашей встроенной системы.Попробуйте найти уже реализованную библиотеку, которую вы можете использовать (проверьте лицензионные ограничения!).

XDR обычно используется в сетевых системах, поскольку им нужен способ перемещения данных независимо от Endianness.Хотя ничто не говорит о том, что его нельзя использовать вне сетей.

0 голосов
/ 18 июля 2011

Вы можете сделать структуру классом с геттерами и сеттерами для членов данных. Методы получения и установки реализованы с чем-то вроде:

int getSomeValue( void ) const {
#if defined( BIG_ENDIAN )
    return _value;
#else
    return convert_to_little_endian( _value );
#endif
}

void setSomeValue( int newValue) {
#if defined( BIG_ENDIAN )
    _value = newValue;
#else
    _value = convert_to_big_endian( newValue );
#endif
}

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

0 голосов
/ 18 июля 2011

Возможно, это не прямой ответ, но, прочитав этот вопрос, можно надеяться, ответит на некоторые ваши вопросы.

...