Как объединить указатель массива? - PullRequest
0 голосов
/ 12 марта 2020

У меня есть следующее struct определение:

typedef struct mb32_packet_t {
    union {
        struct {
            uint16_t preamble;
            uint8_t  system_id;
            uint8_t  message_id;
            uint8_t  reserved;
            uint32_t paylen;
        };
        uint8_t header[9];
    };
    uint8_t *payload;
    uint16_t checksum;
} __attribute__((packed)) mb32_packet_t;

Теперь я хотел бы иметь еще union, чтобы я мог получить указатель uint8_t body[] на весь объект пакета. Примерно так:

typedef struct mb32_packet_t {
    union {
        struct {
            union {
                struct {
                    uint16_t preamble;
                    uint8_t  system_id;
                    uint8_t  message_id;
                    uint8_t  reserved;
                    uint32_t paylen;
                };
                uint8_t header[9];
            };
            uint8_t *payload;
            uint16_t checksum;
        };
        uint8_t body[?];
    };
} __attribute__((packed)) mb32_packet_t;

Проблема в том, что размер поля payload определяется динамически во время выполнения. Есть ли другой способ выполнить sh, отличный от payload фиксированного размера?


Я в основном хочу отправлять объекты этого типа через сетевой сокет, поэтому мне нужен указатель uint8_t это указывает на объект этого типа. На момент отправки объекта я знаю размер всего объекта в байтах.

Ответы [ 3 ]

3 голосов
/ 12 марта 2020

Введение

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

Заголовок фиксированной длины, за которым следует полезная нагрузка переменной длины

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

typedef struct
{
    uint16_t preamble;
    uint8_t  system_id;
    uint8_t  message_id;
    uint8_t  reserved;
    uint32_t paylen;
    uint8_t payload[];
} mb32_packet_t;

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

mb32_packet_t *MyPacket = malloc(sizeof *MyPacket + PayloadLength);

Когда вы передаете такой объект подпрограмме, для которой в качестве аргумента требуется char * или uint8_t * или аналогичный тип Вы можете просто преобразовать указатель:

SendMyMessage(…, (uint8_t *) MyPacket,…);

Это приведение, (uint8_t *) MyPacket, предоставляет указатель на первый байт пакета, запрошенного в вопросе. Нет необходимости вклинивать другого члена в структуру или слой в объединении или другом объявлении.

До введения членов гибкого массива в C 1999 люди использовали один из двух обходных путей для создания структур. с переменным количеством данных. Во-первых, они могут просто определить массив элементов с одним элементом и соответствующим образом скорректировать вычисления пространства:

typedef struct
{
    …
    unsigned char payload[1];
} mb32_packet_t;

mb32_packet_t *MyPacket = malloc(sizeof *MyPacket + PayloadLength - 1);

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

Два, G CC имел собственную предстандартную реализацию гибких элементов массива, просто используя нулевое измерение массива вместо пропуска измерения:

typedef struct
{
    …
    unsigned char payload[0];
} mb32_packet_t;

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

Заголовок фиксированной длины с указателем на полезную нагрузку переменной длины

Форма полезной нагрузки после заголовка, показанная выше, является формой пакета, который я больше всего ожидал бы в пакете обмена сообщениями, потому что он соответствует тому, что аппаратное обеспечение должно поставить «на провод» при отправке байтов по сети: он записывает байты заголовка сопровождаемый байтами данных. Поэтому удобно расположить их таким образом в памяти.

Однако ваш код показывает другой вариант: данные не находятся в пакете, но на них указывает указатель в пакете с uint8_t *payload;. Я подозреваю, что это ошибка, что служба сети или обмена сообщениями действительно хочет иметь элемент гибкого массива, но вы показываете его, за которым следует другой член, uint16_t checksum. Член гибкого массива должен быть последним членом в структуре, поэтому тот факт, что после полезной нагрузки есть другой член, предполагает, что это определение с указателем может быть правильным для службы обмена сообщениями, с которой вы работаете.

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

Как указано выше, вы можете создать указатель uint8_t * на начало пакета с помощью (uint8_t) MyPacket. Если система обмена сообщениями знает о указателе в структуре, это должно работать. Если вы ошиблись, какой должна быть структура пакета, он потерпит неудачу.

Заголовок фиксированной длины, за которым следует пространство полезной нагрузки фиксированной длины

Код в другом месте переполнения стека показывает struct mb32_packet_t с фиксированным объемом пространства для полезной нагрузки:

typedef struct mb32_packet_t {
  uint8_t compid;
  uint8_t servid;
  uint8_t payload[248];
  uint8_t checksum;
} __attribute__((packed)) mb32_packet_s;

В этой форме пакет всегда имеет фиксированный размер, хотя объем пространства используется для полезной нагрузки может варьироваться. Опять же, вы получите uint8_t * указатель на пакет при помощи приведения. Для этого не нужен специальный член.

0 голосов
/ 12 марта 2020

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

Поскольку тело начинается с в известном месте, можно использовать хитрость для доступа к нему, как если бы он был частью структуры. Вы можете объявить его вообще без размера ( «элемент гибкого массива» ) или в виде 0 байтов ( G CC расширение , предшествующее стандарту). Компилятор не выделит для него места, но все равно позволит вам использовать имя для ссылки на конец структуры. Хитрость заключается в том, что вы можете malloc дополнительных байтов после конца структуры, а затем использовать body для ссылки на них.

typedef struct mb32_packet_t {
    union {
        struct {
            uint16_t preamble;
            uint8_t  system_id;
            uint8_t  message_id;
            uint8_t  reserved;
            uint32_t paylen;
        };
        uint8_t header[9];
    };
    uint8_t body[]; // flexible array member

} __attribute__((packed)) mb32_packet_t;

// This is not valid. The body is 0 bytes long, so the write is out of bounds.
mb32_packet_t my_packet;
my_packet.body[0] = 1;

// This is valid though!
mb32_packet_t *my_packet2 = malloc(sizeof(*my_packet2) + 50);
my_packet2->body[49] = 1;

// Alternative way to calculate size
mb32_packet_t *my_packet3 = malloc(offsetof(mb32_packet_t, body[50]));
my_packet3->body[49] = 1;

Элемент гибкого массива должен быть последним. Чтобы получить доступ к контрольной сумме, вам нужно выделить дополнительные 2 байта и использовать указатель arithmeti c. К счастью, это только для контрольной суммы, а не для всего заголовка.

mb32_packet_t *my_packet = malloc(sizeof(*my_packet) + body_size + 2);
uint16_t *pchecksum = (uint16_t*)&my_packet.body[body_size];
// or
uint16_t *pchecksum = (uint16_t*)(my_packet.body + body_size);

После того, как вы заполните заголовок, тело и контрольную сумму, то, поскольку они непрерывны в памяти, указатель на заголовок также указатель на весь объект пакета.

0 голосов
/ 12 марта 2020

Обычно я делаю это так:

typedef struct 
{
    size_t payload_size;
    double x;
    char y[45];
    /* another members */
    unsigned char payload[];
}my_packet_t;

или если ваш компилятор не поддерживает FAM

typedef struct 
{
    size_t payload_size;
    double x;
    char y[45];
    /* another members */
    unsigned char payload[0];
}my_packet_t;

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

...