Сохраняет ли GCC __attribute__((__pack__)) первоначальный порядок?

Цель

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

Вопрос

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

Заметки

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

6 ответов

Решение

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

Раздел §6.7.2.1p13:

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

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

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

Да, __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

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

struct name
{
    short field : 8;
};

Да.

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

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

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

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

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

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

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