Как я могу определить структуру, которая имеет член неизвестного размера?

Вопрос или проблема

Я определил структуру в соответствии с таблицей 17 в спецификации PLDM Base для ответа MultipartReceive. Эта структура используется для получения данных от другого компонента. Согласно спецификации, мы не можем знать размер данных, и ниже него имеется еще одна контрольная сумма crc типа uint32_t. Так как я могу определить эту структуру ответа?

Ответ MultipartReceive

Я решил определить ее следующим образом, и контрольная сумма CRC будет частью data[].

typedef struct {
    uint8_t completion_code;        // код завершения
    uint8_t transfer_flag;
    uint32_t next_transfer_handle;
    uint32_t data_length_bytes;
    uint8_t data[];
} __attribute__((packed)) MultipartReceiveResp_T;

Но мой коллега сказал, что я должен определить эту структуру следующим образом для безопасности:

typedef struct {
    uint8_t completion_code;        // код завершения
    uint8_t transfer_flag;
    uint32_t next_transfer_handle;
    uint32_t data_length_bytes;
    uint8_t data[1];
} __attribute__((packed)) MultipartReceiveResp_T;

Какой вариант лучше? И почему? Я в замешательстве, почему мы должны определить один массив для этого.

Какой вариант лучше? И почему?

uint8_t data[]; лучше, потому что он правильно не указывает размер для массива, который не имеет фиксированного размера. uint8_t data[1]; хуже, потому что он ошибочно сообщает компилятору, что массив имеет один элемент, когда это обычно не так.

Для элемента массива, размер которого будет определен во время выполнения, определение массива с одним элементом или нулевыми элементами было прихотью, использованной в старых версиях C. С тех пор стандарт C изменился, предоставив способ определения гибких элементов массива, используя синтаксис []. Этот новый метод должен быть предпочтителен перед прихотью.

Среди прочего, если вы определите массив с одним элементом и затем получите к нему доступ с помощью, скажем, MyStructure.data[i], поведение компилятора будет соответствовать, если он сделает вывод, что i должно иметь значение ноль, так как это единственное значение, определенное для доступа к массиву, который определен как имеющий один элемент. Поэтому, если у вас был цикл, такой как:

for (int i = 0; i < n; ++i)
{
    …
    t[i] = MyStructure.data[i];
}

компилятор мог бы оптимизировать его до:

if (n)
{
    …
    t[0] = MyStructure.data[0];
}

тем самым исключив все итерации цикла, где i было бы 1, 2, 3 или больше. Как правило, когда вы лжете компилятору, могут произойти плохие вещи.

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

Что касается доступа к контрольной сумме после данных в data, вам нужно будет рассчитать адрес, и затем снова правильный метод доступа к ней может зависеть от обстоятельств. Поскольку data является массивом символьного типа (за исключением какого-либо странного определения uint8_t в вашей реализации C), вы можете легко вычислить N байтов за началом члена data, а затем использовать memcpy для копирования sizeof (uint32) бит в переменную, определенную с типом uint32.

Ответ или решение

Для того чтобы определить структуру, содержащую элемент с неопределённым размером, важно учитывать спецификации языка C и его особенности. В вашем случае вы рассматриваете структуру, которая будет использовать данные из ответа MultipartReceive, а также контрольную сумму (CRC), расположенную в конце этих данных. Поскольку размер массива данных определяется во время выполнения, вы сталкиваетесь с выбором между двумя подходами: использование гибкого массива (uint8_t data[];) или массив фиксированного размера (uint8_t data[1];).

Определение структуры с гибким массивом

Наилучшим подходом будет использование гибкого массива, как в вашем первоначальном определении:

typedef struct {
    uint8_t completion_code;        // код завершения
    uint8_t transfer_flag;          // флаг передачи
    uint32_t next_transfer_handle;   // дескриптор следующей передачи
    uint32_t data_length_bytes;      // длина данных в байтах
    uint8_t data[];                  // гибкий массив данных
} __attribute__((packed)) MultipartReceiveResp_T;

Преимущества использования гибкого массива:

  1. Правильность и точность: Задавая размер массива как data[], вы не указываете фиксированный размер, что соответствует действительности, потому что размер определяется динамически во время выполнения.
  2. Безопасность: Если вы используете data[1], компилятор может интерпретировать это как массив фиксированного размера, что может привести к ошибкам доступа при попытке индексации массива для большей длины, чем одна.
  3. Кроссплатформенность: Гибкие массивы являются стандартом в C99 и поддерживаются большинством современных компиляторов, что позволяет вам писать переносимый код.

Шаблон для доступа к контрольной сумме (CRC)

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

uint32_t crc_checksum = *(uint32_t *)(MyStructure.data + data_length_bytes);

Альтернативное использование фиксированного размера

Ваш коллега предложил альтернативу, использующую uint8_t data[1];. Этот подход был популярен в ранних версиях языка C, но теперь считается устаревшим. Применяя этот метод, вы рискуете столкнуться с ситуациями, когда компилятор будет предполагать, что массив содержит только один элемент, что может привести к неправильному поведению программы и затруднениям при оптимизации кода.

Заключение

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

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

Оцените материал
Добавить комментарий

Капча загружается...