Сохраняет ли __attribute __ GCC ((__packted)) исходный порядок? - PullRequest
25 голосов
/ 18 ноября 2009

Назначение

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

Вопрос

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

Примечания

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

Ответы [ 6 ]

39 голосов
/ 31 октября 2011

Да, __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
27 голосов
/ 18 ноября 2009

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

Раздел § 6.7.2.1p13:

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

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

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

3 голосов
/ 31 октября 2011

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

struct name
{
    short field : 8;
};
3 голосов
/ 18 ноября 2009

Да.

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

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

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

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

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

1 голос
/ 18 ноября 2009

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

...