GCC __attribute __ ((__ставляется__)) сохраняет первоначальный заказ?

Цель

Я пишу сетевую программу в C (в частности gnu89), и я хотел бы упростить вещи, переинтерпретируя определенный struct X как большой массив байтов (aka char), отправляя байты по сети, и переинтерпретировать их как struct X с другой стороны. Для этого я решил использовать gcc __attribute __ ((__ставляется__)). Я сделал все возможное, чтобы убедиться, что это сделано правильно (т.е. Я учитывал сущность и другие связанные проблемы).

Вопрос

Кроме того, что struct X как можно меньше, gcc гарантирует, что a struct, определенный с __attribute __ ((__ставляется__)), сохраняет исходный порядок? Я провел много исследований, и мне еще предстоит найти документацию о том, существует ли эта гарантия.

Примечания

Можно с уверенностью предположить, что и отправитель, и получатель не будут иметь проблем с переносимостью (например, sizeof(int) на сервере равно sizeof(int) на клиенте).

Ответ 1

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

Раздел §6.7.2.1p13:

Внутри объект структуры, небитовое поле членов и подразделений, в которых битовые поля имеют адреса, которые увеличение порядка, в котором они объявлены.

и в документации для упакованного атрибута четко указано, что затронуто только отступы/выравнивание:

Упакованный атрибут указывает, что поле переменной или структуры должно иметь как можно меньше alignment-one byte для переменной и один бит для поля, если только вы укажите большее значение с помощью выровненный атрибут.

Ответ 2

Да, __attribute__((packed)) (не требуется второй набор символов подчеркивания) - это правильный способ реализации бинарных (то есть нетекстовых) сетевых протоколов. Между элементами не будет пробелов.

Однако вы должны понимать, что packed не только упаковывает структуру, но также:

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

Однако, компилятор будет заниматься только несоосностью, если вы напрямую обращаетесь к членам структуры. Вы должны никогда не создавать указатель на элемент упакованной структуры (кроме тех случаев, когда вы знаете, что для выравнивания элемента требуется 1, например char или другая упакованная структура). Следующий код C демонстрирует проблему:

#include <stdio.h>
#include <inttypes.h>
#include <arpa/inet.h>

struct packet {
    uint8_t x;
    uint32_t y;
} __attribute__((packed));

int main ()
{
    uint8_t bytes[5] = {1, 0, 0, 0, 2};
    struct packet *p = (struct packet *)bytes;

    // compiler handles misalignment because it knows that
    // "struct packet" is packed
    printf("y=%"PRIX32", ", ntohl(p->y));

    // compiler does not handle misalignment - py does not inherit
    // the packed attribute
    uint32_t *py = &p->y;
    printf("*py=%"PRIX32"\n", ntohl(*py));
    return 0;
}

В системе x86 (которая не обеспечивает выравнивание доступа к памяти) это приведет к созданию

y=2, *py=2

как ожидалось. С другой стороны, на моей плате ARM Linux, например, он произвел, казалось бы, неправильный результат

y=2, *py=1

Ответ 3

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

struct foo
{
  short someField : 16 __attribute__ ((packed));
};

Это гарантирует, что someField будет сохранен как 16 бит и не будет изменен или изменен, чтобы соответствовать границам байтов.

Ответ 4

Да.

Однако использование __attribute__((__packed__)) не является хорошим способом делать то, что вы делаете.

  • Он не разрешает проблемы порядка байтов
  • Доступ к структуре будет медленным.
  • Хотя популярность gcc привела к ситуации, когда другие компиляторы часто реализуют расширения gcc, использование этого расширения для компилятора означает, что у вас нет соответствующей программы на C. Это означает, что если другой компилятор или даже будущий gcc изменяет упакованный или вообще не реализует его, вам не повезло и он не может даже жаловаться кому-либо. Gcc может отбросить его завтра и по-прежнему быть компилятором C99. (Хорошо, что точная вещь маловероятна.) Большинство из нас пытаются писать соответствующие программы не потому, что у нас есть какая-то абстрактная враждебность к использованию программного обеспечения поставщика или желание получить академический стандарт чистоты кода, а скорее потому, что мы знаем, что только соответствующие языковые функции имеют точную и общую спецификацию, поэтому гораздо легче зависеть от того, как наш код делает правильные вещи изо дня в день и система в систему, если мы это сделаем.
  • Вы изобретаете колесо; эта проблема уже решена стандартно: см. YAML, XML, и JSON.
  • Если такой низкоуровневый протокол, который недоступен YAML, XML и JSON, вы действительно должны брать индивидуальные фундаментальные типы, применять свою хост-версию hton?() и ntoh?() и memcpy() в выходной буфер. Я понимаю, что существует давняя традиция читать и писать прямо из структур, но я также долгое время исправлял этот код позже, когда он был перемещен из 32-битных в 64-разрядные среды...

Ответ 5

Основываясь на том, что вы пытаетесь сделать, я настоятельно рекомендую вам также использовать типы данных фиксированного размера (например, uint32_t, int16_t и т.д.), которые находятся в stdint.h. Использование типов данных фиксированного размера будет препятствовать вам выполнять следующие действия:

struct name
{
    short field : 8;
};

Ответ 6

Да, C имеет гарантию того, что элементы структуры не будут переупорядочены. (Могут быть расширения или фантастические системы оптимизации, которые могут изменить это, но не по умолчанию в gcc.)