Почему приведение из массива к указателю может испортить мои данные?
Мой друг опубликовал эту проблему на Facebook на днях, и я не могу понять ее. Он пишет клиент и сервер, используя протокол cubesat. По какой-то причине, когда он преобразует элемент данных структуры протокола в указатель, его данные выглядят искаженными.
Фрагмент кода клиента:
uint32_t data[3] = { 1234U, 5678U, 9101U };
memcpy(packet->data32, data, sizeof(data));
packet->length = sizeof(data);
csp_send(connection, packet, 1000);
Фрагмент кода сервера:
uint32_t *data = (uint32_t *)(packet->data32);
printf("Packet received on %i: %u\r\n", PORT, data[0]);
printf("Packet received on %i: %u\r\n", PORT, data[1]);
printf("Packet received on %i: %u\r\n", PORT, data[2]);
printf("Packet received on %i: %u, %u, %u\r\n", PORT, data[0], data[1], data[2]);
Выведите этот код:
Packet received on 15: 2182284498
Packet received on 15: 5678
Packet received on 15: 9101
Packet received on 15: 80904723, 372113408, 596443136
Вывод случайного читателя этого кода будет ожидать:
Packet received on 15: 1234
Packet received on 15: 5678
Packet received on 15: 9101
Packet received on 15: 1234, 5678, 9101
После некоторого возни он сказал мне, что он получает правильный результат, если он не разыгрывает data32
член структуры к uint32_t*
,
Из моего собственного исследования, packet
имеет тип csp_packet_t
, который определяется как:
typedef struct __attribute__((__packed__)) {
uint8_t padding[CSP_PADDING_BYTES]; // Interface dependent padding
uint16_t length; // Length field must be just before CSP ID
csp_id_t id; // CSP id must be just before data
union {
uint8_t data[0]; // This just points to the rest of the buffer, without a size indication.
uint16_t data16[0]; // The data 16 and 32 types makes it easy to reference an integer (properly aligned)
uint32_t data32[0]; // - without the compiler warning about strict aliasing rules.
};
} csp_packet_t;
Полный заголовочный файл находится здесь.
Это GNU C, поэтому допустимы массивы нулевой длины.
Я не знаю размер слова или порядковый номер архитектуры с обеих сторон.
Итак, проще говоря - что здесь происходит? Почему актерский состав имеет значение?
3 ответа
Я думаю, что это проблема выравнивания.
Цитирование Fritzone:
2182284498 - 0x821304D2, где 0x04d2 - 1234, а остальные, возможно, являются пакетными данными.
Это потому, что объединение является членом упакованной структуры, и оно не соответствует 16 битам. При обращении к члену объединения в упакованной структуре компилятор работает неким образом, чтобы гарантировать, что упакованные (выровненные) данные будут получены правильно. Тем не менее, при приведении к uint32_t*
компилятор теряет детали упаковки, и я полагаю, что он предполагает, что он обращается к данным, которые правильно выровнены.
2182284498
является 0x821304D2
где 0x04d2
является 1234
а остальное возможно это пакетные данные. Больше, не зная как csp_send
и соответствующий приемник выглядит (то есть: показать больше кода) не представляется возможным сказать.
И эта строка: memcpy(packet->data32, data, sizeof(data));
на самом деле ошибка переполнения буфера... так как вы не выделяете достаточно байтов для packet->data32
Массивы не могут иметь нулевой размер. Этот код использует какой-то нестандартный C даже для компиляции union. Так что же происходит внутри этого союза, никто не знает. Это неопределенное поведение, что касается языка Си.
Так как программа делает "что угодно", когда сталкивается с неопределенным поведением, она работает как положено.