#pragma pack effect - PullRequest
       27

#pragma pack effect

202 голосов
/ 23 июля 2010

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

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

Ответы [ 10 ]

370 голосов
/ 23 июля 2010

#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.

22 голосов
/ 23 июля 2010

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

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

15 голосов
/ 23 июля 2010

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

struct foo { 
    char a;
    int b;
};

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

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

7 голосов
/ 23 июля 2010

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

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

2 голосов
/ 23 июля 2010

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

2 голосов
/ 23 июля 2010

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

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

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

1 голос
/ 23 июля 2010

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

1 голос
/ 23 июля 2010

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

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

1 голос
/ 23 июля 2010

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

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

0 голосов
/ 09 февраля 2016

Обратите внимание, что существуют и другие способы обеспечения согласованности данных, предлагаемые #pragma pack (например, некоторые люди используют #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 в точности соответствует количеству байтов, но в struct b добавлено заполнение (см. this для получения подробной информации о заполнении).Делая это в отличие от пакета #pragma, вы можете контролировать преобразование «проводного формата» в соответствующие типы.Например, "char two [2]" в "short int" и так далее.

...