#pragma pack effect
Мне было интересно, может ли кто-нибудь объяснить мне, что делает препроцессор #pragma pack
, и что еще более важно, почему он хотел бы использовать его.
Я проверил страницу MSDN , которая предложила некоторое понимание, но я надеялся услышать больше от людей с опытом. Раньше я видел это в коде, хотя, похоже, я больше не могу найти.
- «#Include» текстовый файл в программе на языке C как символ
- Предупреждение компилятора "No newline at end of file"
- Каково возможное использование для параметра #define для if (false) {} else for??
- Использование X-Macros в реальном мире
- Перегрузка макроса по количеству аргументов
- Тип переменных #define
- Каковы допустимые символы для имен макросов?
- Являются ли typedef и #define одинаковыми в c?
- Массив размера массива, который отклоняет указатели
- Выход препроцессора
- Определения макросов C # в препроцессоре
- Почему macros препроцессора злые и какие альтернативы?
- Макро против функции в C
#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
специально используется для указания того, что структурированная #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 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]; };
Элементы данных (например, члены classов и структур) обычно выравниваются по границам WORD или DWORD для процессоров текущего поколения, чтобы улучшить время доступа. Получение DWORD по адресу, не делящемуся на 4, требует, по крайней мере, одного дополнительного цикла ЦП на 32-битном процессоре. Итак, если у вас есть, например, три элемента char a, b, c;
, они, как правило, занимают 6 или 12 байт памяти.
#pragma
позволяет вам переопределить это для достижения более эффективного использования пространства за счет скорости доступа или для согласованности сохраненных данных между различными целями компилятора. Мне было очень весело с этим переходом от 16-битного до 32-битного кода; Я ожидаю, что перенос на 64-битный код вызовет одни и те же головные боли для некоторого кода.
Компилятор может размещать элементы структуры на определенных границах байтов по причинам производительности в конкретной архитектуре. Это может оставить неиспользуемое дополнение между членами. Структурные уплотнения заставляют элементы быть смежными.
Это может быть важно, например, если вам требуется, чтобы структура соответствовала определенному файлу или формату связи, где данные, в которых данные должны находиться в определенных положениях в последовательности. Однако такое использование не касается проблем, связанных с контентом, поэтому, хотя и используется, оно может быть не переносимым.
Он также может точно наложить структуру внутреннего регистра некоторого устройства ввода-вывода, такого как UART или USB-controller, например, чтобы доступ к регистру осуществлялся через структуру, а не прямые адреса.
Компилятор может согласовывать элементы в структурах для достижения максимальной производительности на определенной платформе. Директива #pragma pack
позволяет вам контролировать это выравнивание. Обычно вы должны оставить его по умолчанию для оптимальной производительности. Если вам нужно передать структуру на удаленную машину, вы обычно будете использовать #pragma pack 1
чтобы исключить нежелательное выравнивание.
Скорее всего, вы захотите использовать это, если бы вы кодировали какое-либо оборудование (например, устройство с отображением памяти), которое имело строгие требования к упорядочению и выравниванию регистров.
Однако это похоже на довольно тупой инструмент для достижения этой цели. Лучшим подходом было бы закодировать мини-драйвер в ассемблере и дать ему интерфейс вызова C, а не возиться с этой прагмой.
Раньше я использовал его в коде, но только для взаимодействия с устаревшим кодом. Это приложение Mac OS X Cocoa, которое необходимо было загружать файлы предпочтений из более ранней версии Carbon (которая сама была обратно совместима с оригинальной версией M68k System 6.5 … вы поняли эту идею). Файлы предпочтений в исходной версии были двоичным дампом структуры конфигурации, которая использовала #pragma pack(1)
чтобы избежать лишнего места и сохранения мусора (то есть байтов заполнения, которые в противном случае были бы в структуре).
Оригинальные авторы кода также использовали #pragma pack(1)
для хранения структур, которые использовались в качестве сообщений в межпроцессной связи. Я думаю, что причина здесь заключалась в том, чтобы избежать возможности неизвестных или измененных размеров заполнения, поскольку код иногда просматривал определенную часть структуры сообщения, подсчитывая количество байтов с начала (ewww).
Я видел, как люди использовали его, чтобы убедиться, что структура занимает целую строку кэша, чтобы предотвратить ложное совместное использование в многопоточном контексте. Если у вас будет большое количество объектов, которые по умолчанию будут упакованы по-умолчанию, это может сэкономить память и повысить производительность кеша, чтобы упаковать их более жестко, хотя неравномерный доступ к памяти обычно замедляет работу, поэтому может быть недостаток.
Обратите внимание, что существуют другие способы достижения согласованности данных, которые предлагает пакет #pragma (например, некоторые люди используют пакет #pragma (1) для структур, которые должны быть отправлены по сети). Например, см. Следующий код и его последующий вывод:
#include 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 добавлена добавка (подробнее см. Дополнение). Выполняя это, в отличие от пакета #pragma, вы можете управлять преобразованием «формата проводов» в соответствующие типы. Например, «char two [2]» в «короткий int» и так далее.