#pragma pack effect
Мне было интересно, если кто-то может объяснить мне, что #pragma pack
заявление препроцессора делает, и что более важно, почему кто-то хотел бы использовать его.
Я проверил страницу MSDN, которая дала некоторое представление, но я надеялся услышать больше от людей с опытом. Я видел это в коде раньше, хотя я не могу найти, где больше.
10 ответов
#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.
#pragma
используется для отправки непереносимых (как только в этом компиляторе) сообщений компилятору. Такие вещи, как отключение определенных предупреждений и структура упаковки, являются общими причинами. Отключение определенных предупреждений особенно полезно, если вы компилируете предупреждения, когда включен флаг ошибок.
#pragma pack
в частности, используется для указания того, что упаковываемая структура не должна выравнивать свои элементы. Это полезно, когда у вас есть отображенный в памяти интерфейс для части аппаратного обеспечения и вам нужно точно контролировать, куда указывают различные члены структуры. В частности, это не очень хорошая оптимизация скорости, так как большинство машин гораздо быстрее справляются с согласованными данными.
Он сообщает компилятору границу для выравнивания объектов в структуре. Например, если у меня есть что-то вроде:
struct foo {
char a;
int b;
};
На типичном 32-битном компьютере вы обычно "хотите" иметь 3 байта заполнения a
а также b
чтобы b
попадет на 4-байтовую границу, чтобы максимизировать скорость доступа (и это обычно происходит по умолчанию).
Однако, если вам нужно соответствовать внешне определенной структуре, вы хотите убедиться, что компилятор разметит вашу структуру точно в соответствии с этим внешним определением. В этом случае вы можете дать компилятору #pragma pack(1)
чтобы запретить вставку какого-либо отступа между членами - если определение структуры включает заполнение между членами, вы вставляете его явно (например, обычно с именами членов unusedN
или же ignoreN
или что-то в этом порядке).
#
Pragma Pack (N) просто устанавливает новое выравнивание.#
pragma pack () устанавливает выравнивание на тот, который действовал при запуске компиляции.#
пакет pragma (push[,n]) помещает текущую настройку выравнивания во внутренний стек и затем при необходимости устанавливает новое выравнивание.#
Пакет pragma (pop) восстанавливает настройку выравнивания, сохраненную в верхней части внутреннего стека (и удаляет эту запись стека). Обратите внимание, что #pragma pack([n]) не влияет на этот внутренний стек; таким образом, можно получить пакет #pragma (push), за которым следуют несколько экземпляров #pragma pack(n) и завершить их одним пакетом #pragma (pop).
Примеры:
#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];
};
Элементы данных (например, члены классов и структур) обычно выровнены по границам WORD или DWORD для процессоров текущего поколения, чтобы сократить время доступа. Для извлечения DWORD по адресу, который не делится на 4, требуется как минимум один дополнительный цикл ЦП на 32-разрядном процессоре. Итак, если у вас есть, например, три члена чар char a, b, c;
они, как правило, занимают 6 или 12 байт памяти.
#pragma
позволяет вам переопределить это для достижения более эффективного использования пространства за счет скорости доступа или для согласованности хранимых данных между различными целями компилятора. Мне было очень весело с этим переходом с 16-битного на 32-битный код; Я ожидаю, что портирование на 64-битный код вызовет такие же головные боли для некоторого кода.
Компилятор может выравнивать элементы в структурах для достижения максимальной производительности на определенной платформе. #pragma pack
Директива позволяет вам контролировать это выравнивание. Обычно вы должны оставить его по умолчанию для оптимальной производительности. Если вам нужно передать структуру на удаленную машину, вы обычно будете использовать #pragma pack 1
исключить любое нежелательное выравнивание.
Компилятор может размещать элементы структуры на определенных границах байтов из соображений производительности на конкретной архитектуре. Это может оставить неиспользованные отступы между членами. Упаковка структуры заставляет участников быть смежными.
Это может быть важно, например, если вам требуется, чтобы структура соответствовала определенному файлу или формату связи, где данные, которые вам нужны, должны находиться в определенных позициях в последовательности. Однако такое использование не решает проблемы с порядком байтов, поэтому, хотя оно и используется, оно не может быть переносимым.
Он также может точно перекрывать структуру внутреннего регистра некоторого устройства ввода-вывода, такого как, например, контроллер UART или USB, для того, чтобы доступ к регистру осуществлялся через структуру, а не через прямые адреса.
Скорее всего, вы захотите использовать это, только если кодируете какое-то оборудование (например, устройство с отображенной памятью), которое имеет строгие требования к упорядочению и выравниванию регистров.
Тем не менее, это выглядит довольно тупым инструментом для достижения этой цели. Лучшим подходом было бы написать мини-драйвер на ассемблере и дать ему интерфейс вызова C, а не возиться с этой прагмой.
Я видел, как люди используют его, чтобы убедиться, что структура занимает целую строку кэша, чтобы предотвратить ложное совместное использование в многопоточном контексте. Если у вас будет большое количество объектов, которые по умолчанию будут свободно упакованы, это может сэкономить память и улучшить производительность кэша, чтобы упаковать их, хотя доступ к памяти без выравнивания, как правило, замедляет работу, поэтому возможны недостатки.
Я использовал его в коде раньше, но только для взаимодействия с устаревшим кодом. Это было приложение Mac OS X Cocoa, которое должно было загружать файлы предпочтений из более ранней версии Carbon (которая была обратно совместима с исходной версией M68k System 6.5... вы понимаете). Файлы настроек в исходной версии представляли собой двоичный дамп структуры конфигурации, в котором использовались #pragma pack(1)
чтобы не занимать дополнительное место и не экономить мусор (то есть байты заполнения, которые в противном случае были бы в структуре).
Оригинальные авторы кода также использовали #pragma pack(1)
хранить структуры, которые использовались в качестве сообщений в межпроцессном взаимодействии. Я думаю, что причина здесь состояла в том, чтобы избежать возможности неизвестных или измененных размеров заполнения, так как код иногда просматривал определенную часть структуры сообщения путем подсчета количества байтов с начала (ewww).
Обратите внимание, что есть и другие способы обеспечения согласованности данных, которые предлагает пакет #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
Обратите внимание, что размер структуры a точно соответствует количеству байтов, но в структуре b добавлено заполнение (см. Подробности по заполнению). Делая это в отличие от пакета #pragma, вы можете контролировать преобразование "проводного формата" в соответствующие типы. Например, "char two[2]" в "short int" и так далее.
Почему его хотят использовать?
Чтобы уменьшить память конструкции
Почему нельзя его использовать?
- Это может привести к снижению производительности, потому что некоторые системы лучше работают с согласованными данными.
- Некоторые машины не смогут прочитать невыровненные данные
- Код не переносится