Вопрос или проблема
Я определил структуру в соответствии с таблицей 17 в спецификации PLDM Base для ответа MultipartReceive. Эта структура используется для получения данных от другого компонента. Согласно спецификации, мы не можем знать размер данных, и ниже него имеется еще одна контрольная сумма crc типа uint32_t. Так как я могу определить эту структуру ответа?
Я решил определить ее следующим образом, и контрольная сумма 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;
Преимущества использования гибкого массива:
- Правильность и точность: Задавая размер массива как
data[]
, вы не указываете фиксированный размер, что соответствует действительности, потому что размер определяется динамически во время выполнения. - Безопасность: Если вы используете
data[1]
, компилятор может интерпретировать это как массив фиксированного размера, что может привести к ошибкам доступа при попытке индексации массива для большей длины, чем одна. - Кроссплатформенность: Гибкие массивы являются стандартом в C99 и поддерживаются большинством современных компиляторов, что позволяет вам писать переносимый код.
Шаблон для доступа к контрольной сумме (CRC)
Чтобы получить доступ к контрольной сумме после данных, вы можете использовать указатель для вычисления адреса, к которому нужно обратиться:
uint32_t crc_checksum = *(uint32_t *)(MyStructure.data + data_length_bytes);
Альтернативное использование фиксированного размера
Ваш коллега предложил альтернативу, использующую uint8_t data[1];
. Этот подход был популярен в ранних версиях языка C, но теперь считается устаревшим. Применяя этот метод, вы рискуете столкнуться с ситуациями, когда компилятор будет предполагать, что массив содержит только один элемент, что может привести к неправильному поведению программы и затруднениям при оптимизации кода.
Заключение
Использование гибкого массива — это предпочтительный и безопасный способ определения структуры, которая должна работать с переменными размерами массивов. Это не только упрощает доступ к данным, но и гарантирует, что вы не столкнётесь с потенциальными ошибками кода при работе с массивами, размеры которых известны только во время выполнения.
В качестве развития ваших навыков важно всегда проверять современные стандарты языка и их применение. Оставайтесь в курсе соответствующих практик и никогда не полагайтесь на устаревшие подходы, так как они могут нанести ущерб качеству и безопасности вашего кода.