Почему приведение из массива к указателю может испортить мои данные?

Мой друг опубликовал эту проблему на 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. Так что же происходит внутри этого союза, никто не знает. Это неопределенное поведение, что касается языка Си.

Так как программа делает "что угодно", когда сталкивается с неопределенным поведением, она работает как положено.

Другие вопросы по тегам