Как я могу быть уверен, что эта структура C упакована как в 32-битных, так и в 64-битных системах? Всегда ли необходим __attribute __ ((упакованный))? - PullRequest
0 голосов
/ 13 января 2019

Я пытаюсь создать простой пользовательский протокол прикладного уровня, содержащий метку времени и некоторую другую полезную информацию, чтобы выполнить несколько измерений сети между различными системами Linux.

Реализация, в моей первоначальной идее, должна быть максимально переносимой между различными платформами (x86, ARM, ...), насколько это касается систем Linux.

Для управления заголовком я создал следующую структуру:

struct myhdr {
    __u8 reserved; // 1 byte
    __u8 ctrl; // 1 byte
    __u16 id; // 2 bytes
    __u16 seq; // 2 bytes
    __u16 len; // 2 bytes
    struct timeval sendtime; // 8 or 16 bytes
};

После чего некоторые полезные данные могут присутствовать или не присутствовать (если len = 0). Поскольку эти данные должны быть отправлены по сети, мне нужна, если я не ошибаюсь, структура, которая должна быть упакована, без каких-либо выравниваний.

Я сомневаюсь, действительно ли это можно считать уже упакованным или нет, в основном из-за наличия struct timeval для переноса метки времени.

В 32-битных системах struct timeval должно быть 8 байтов. В 64-битных системах это должно быть 16 байтов (просто протестируйте это, напечатав sizeof(struct timeval)).

Безопасно ли в 32-битных системах предполагать, что он уже упакован, из-за того, что 1 + 1 + 2 + 2 + 2 байта = 8 байтов, что составляет размер sendtime? Или в любом случае будет добавлен отступ, чтобы выровнять каждое поле по последнему, которое является самым большим?

Что происходит тогда в 64-битных системах, где последнее поле составляет 16 байтов? Я думаю, что в любом случае структура больше не будет «упакована по макету» (это правильно?).

Является ли добавление __attribute__((packed)) достаточным и всегда необходимым для обеспечения того, чтобы структура была упакована при компиляции кода для разных платформ? Есть ли лучшие решения?

1 Ответ

0 голосов
/ 13 января 2019

Если вы определяете проводной протокол, вам действительно нужно использовать свой собственный тип. Чтобы быть в безопасности, вы должны использовать 64 бита для секунд с 1970 года, даже если многие 32-битные системы все еще используют 32-битный счетчик.

struct timeval происходит из системного заголовка и может иметь практически любой размер, начиная с 8 байтов. Я уверен, что есть 32-битные системы с размером 12 (64-битный time_t для tv_sec, чтобы избежать проблемы Y2038, 32-битный long / suseconds_t для tv_usec). Кроме того, POSIX требует, чтобы struct timeval имел определенных членов. Некоторые системы имеют явные поля в своих системных заголовках, чтобы избежать неявного заполнения компилятором, что приводит к дальнейшим различиям в (гипотетическом) поведении упаковки.

И хотя __attribute__ ((pack)) не применяется рекурсивно (поэтому sizeof (p->sendtime) будет равняться sizeof (struct timeval), оно все же уменьшает выравнивание всей структуры, включая элемент sendtime, до 1, поэтому элемент может быть выровнен и не подходит для прямого использования с функциями, которые ожидали struct timeval *.

...