#pragma pack effect

Мне было интересно, может ли кто-нибудь объяснить мне, что делает оператор препроцессора #pragma pack, и, что более важно, почему он захочет его использовать.

Я проверил страницу MSDN, которая дала некоторую информацию, но я надеялся услышать больше от людей с опытом. Я видел это в коде раньше, хотя я не могу найти, где больше.

Ответ 1

#pragma pack указывает компилятору упаковать элементы структуры с определенным выравниванием. Большинство компиляторов, когда вы объявляете структуру, вставляют заполнение между членами, чтобы гарантировать, что они выровнены по соответствующим адресам в памяти (обычно кратным размеру шрифта). Это позволяет избежать снижения производительности (или явной ошибки) на некоторых архитектурах, связанных с доступом к переменным, которые не выровнены должным образом. Например, даны 4-байтовые целые числа и следующая структура:

struct Test
{
   char AA;
   int BB;
   char CC;
};

Компилятор может разместить структуру в памяти следующим образом:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

и sizeof(Test) будет 4 × 3 = 12, даже если он содержит только 6 байтов данных. Наиболее распространенный вариант использования #pragma (насколько мне известно) - это работа с аппаратными устройствами, когда вам нужно убедиться, что компилятор не вставляет заполнение в данные, а каждый элемент следует предыдущему. С помощью #pragma pack(1) приведенная выше структура будет выглядеть следующим образом:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

И sizeof(Test) будет 1 × 6 = 6.

С #pragma pack(2) приведенная выше структура будет выглядеть следующим образом:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

И sizeof(Test) будет 2 × 4 = 8.

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

struct Test
{
   char AA;
   char CC;
   int BB;
};

и с помощью #pragma pack(2) структура будет выглядеть следующим образом:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

и sizeOf(Test) будет 3 × 2 = 6.

Ответ 2

#pragma используется для отправки сообщений, не переносимых (как в этом компиляторе), компилятору. Такие причины, как отключение определенных предупреждений и упаковочных структур, являются общими причинами. Отключение определенных предупреждений особенно полезно, если вы скомпилируете предупреждения с включенным флагом ошибок.

#pragma pack специально используется, чтобы указать, что структурированная упаковка не должна выравниваться. Это полезно, когда у вас есть интерфейс с отображением памяти для части аппаратного обеспечения и нужно иметь возможность точно контролировать, где указывают разные члены структуры. Это, в частности, не хорошая оптимизация скорости, поскольку большинство машин намного быстрее работают с выровненными данными.

Ответ 3

Он сообщает компилятору о границе для выравнивания объектов в структуре. Например, если у меня есть что-то вроде:

struct foo { 
    char a;
    int b;
};

С типичной 32-разрядной машиной вы обычно "хотите" иметь 3 байта заполнения между a и b, чтобы b приземлялся на 4-байтовой границе, чтобы максимизировать скорость доступа (и то, что обычно будет происходить по умолчанию).

Если, однако, вам нужно сопоставить внешнюю структуру, которую вы хотите, чтобы компилятор изложил вашу структуру именно в соответствии с этим внешним определением. В этом случае вы можете предоставить компилятору #pragma pack(1), чтобы он не вставлял никаких дополнений между членами - если определение структуры включает в себя дополнение между членами, вы вставляете его явно (например, обычно с членами с именем unusedN или ignoreN, или что-то в этом порядке).

Ответ 4

Элементы данных (например, члены классов и структур) обычно выравниваются по границам WORD или DWORD для процессоров текущего поколения, чтобы улучшить время доступа. Получение DWORD по адресу, не делящемуся на 4, требует, по крайней мере, одного дополнительного цикла ЦП на 32-битном процессоре. Итак, если у вас есть, например, три char участника char a, b, c;, они на самом деле имеют тенденцию брать 6 или 12 байтов памяти.

#pragma позволяет вам переопределить это для более эффективного использования пространства за счет скорости доступа или для согласованности сохраненных данных между различными целями компилятора. Мне было очень весело с этим переходом от 16-битного до 32-битного кода; Я ожидаю, что перенос на 64-битный код вызовет одни и те же головные боли для некоторого кода.

Ответ 5

Компилятор может размещать элементы структуры на определенных границах байтов по причинам производительности для конкретной архитектуры. Это может оставить неиспользуемое дополнение между членами. Структурные уплотнения заставляют элементы быть смежными.

Это может быть важно, например, если вам требуется, чтобы структура соответствовала конкретному файлу или формату связи, где данные, в которых данные должны находиться в определенных положениях в последовательности. Однако такое использование не касается проблем с конечным пользователем, поэтому, хотя оно и используется, оно может быть не переносимым.

Он также может точно накладывать структуру внутреннего регистра некоторых устройств ввода-вывода, таких как, например, UART или USB-контроллер, чтобы доступ к регистру осуществлялся через структуру, а не прямые адреса.

Ответ 6

Компилятор может согласовывать элементы в структурах для достижения максимальной производительности на определенной платформе. Директива #pragma pack позволяет вам контролировать это выравнивание. Обычно вы должны оставить его по умолчанию для оптимальной производительности. Если вам нужно передать структуру на удаленный компьютер, вы обычно будете использовать #pragma pack 1, чтобы исключить любое нежелательное выравнивание.

Ответ 7

  • # pragma pack (n) просто устанавливает новое выравнивание.
  • # pragma pack() устанавливает выравнивание по отношению к тому, которое было в действительности при запуске компиляции.
  • # pragma pack (push [, n]) подталкивает текущую настройку выравнивания во внутреннем стеке, а затем дополнительно устанавливает новое выравнивание.
  • # pragma pack (pop) восстанавливает настройку выравнивания на том, что хранится в верхней части внутреннего стека (и удаляет эту запись стека). Обратите внимание, что #pragma pack ([n]) не влияет на этот внутренний стек; таким образом, возможно иметь пакет #pragma (push), за которым следуют несколько экземпляров #pragma pack (n) и завершаться одним пакетом #pragma (поп).

Примеры:

#pragma pack(push, 1) // exact fit - no padding
struct MyStruct
{
  char b; 
  int a; 
  int array[2];
};
#pragma pack(pop) //back to whatever the previous packing mode was 

Or

#pragma pack(1) // exact fit - no padding
struct MyStruct
{
  char b; 
  int a; 
  int array[2];
};
#pragma pack() //back to whatever the previous packing mode was 


Or 

#pragma pack(1) // exact fit - no padding
struct MyStruct
{
  char b; 
  int a; 
  int array[2];
}; 

Ответ 8

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

Однако это похоже на довольно тупой инструмент для достижения этой цели. Лучшим подходом было бы закодировать мини-драйвер в ассемблере и дать ему интерфейс вызова C, а не возиться с этой прагмой.

Ответ 9

Я использовал его в коде раньше, хотя только для взаимодействия с устаревшим кодом. Это приложение Mac OS X Cocoa, которое необходимо было загружать файлы предпочтений из более ранней версии Carbon (которая сама была обратно совместима с исходной версией M68k System 6.5... вы поняли эту идею). Файлы предпочтений в исходной версии были двоичным дампом структуры конфигурации, который использовал #pragma pack(1), чтобы избежать лишнего места и сохранения мусора (то есть байтов заполнения, которые в противном случае были бы в структуре).

Оригинальные авторы кода также использовали #pragma pack(1) для хранения структур, которые использовались в качестве сообщений в межпроцессной связи. Я думаю, что причина здесь заключалась в том, чтобы избежать возможности неизвестных или измененных размеров заполнения, поскольку код иногда смотрел на определенную часть структуры сообщения, подсчитывая количество байтов с начала (ewww).

Ответ 10

Я видел, как люди использовали его, чтобы убедиться, что структура занимает целую строку кэша, чтобы предотвратить ложное совместное использование в многопоточном контексте. Если у вас будет большое количество объектов, которые по умолчанию будут упакованы по-умолчанию, они могут сэкономить память и повысить производительность кеша, чтобы упаковать их более жестко, хотя неравномерный доступ к памяти обычно замедляет работу, поэтому может быть недостаток.

Ответ 11

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

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

Выход следующий: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48

Обратите внимание, как размер struct a является именно тем, что является байтом, но структура b добавлена ​​добавка (см. this для получения подробной информации о обивка). Выполняя это, в отличие от пакета #pragma, вы можете управлять преобразованием "формата проводов" в соответствующие типы. Например, "char два [2]" в "короткий int" и т.д.