Мне было интересно, может ли кто-нибудь объяснить мне, что #pragma pack
делает оператор препроцессора, и, что более важно, почему он захочет его использовать.
Я проверил страницу MSDN , которая дала некоторое представление, но я надеялся услышать больше от людей с опытом. Я видел это в коде раньше, хотя я не могу найти, где больше.
c
c-preprocessor
pragma-pack
Cenoc
источник
источник
#pragma
директивы, они определены реализацией.A mod s = 0
где A - адрес, а s - размер типа данных; это проверяет, не смещены ли данные.Ответы:
#pragma pack
указывает компилятору упаковать элементы структуры с определенным выравниванием. Большинство компиляторов, когда вы объявляете структуру, вставляют заполнение между членами, чтобы гарантировать, что они выровнены по соответствующим адресам в памяти (обычно кратным размеру типа). Это позволяет избежать снижения производительности (или явной ошибки) на некоторых архитектурах, связанных с доступом к переменным, которые не выровнены должным образом. Например, даны 4-байтовые целые и следующая структура:Компилятор может разместить структуру в памяти следующим образом:
и
sizeof(Test)
будет 4 × 3 = 12, даже если он содержит только 6 байтов данных. Наиболее распространенный вариант использования#pragma
(насколько мне известно) - при работе с аппаратными устройствами, когда вам нужно убедиться, что компилятор не вставляет заполнение в данные, а каждый элемент следует предыдущему. С#pragma pack(1)
, структура выше будет выглядеть так:И
sizeof(Test)
будет 1 × 6 = 6.С
#pragma pack(2)
, структура выше будет выглядеть так:И
sizeof(Test)
будет 2 × 4 = 8.Порядок переменных в структуре также важен. С переменными, упорядоченными следующим образом:
и с
#pragma pack(2)
, структура будет выглядеть так:и
sizeOf(Test)
будет 3 × 2 = 6.источник
#pragma
используется для отправки непереносимых (как только в этом компиляторе) сообщений компилятору. Такие вещи, как отключение определенных предупреждений и структура упаковки, являются общими причинами. Отключение определенных предупреждений особенно полезно, если вы компилируете предупреждения, когда включен флаг ошибок.#pragma pack
в частности, используется для указания того, что упаковываемая структура не должна выравнивать свои элементы. Это полезно, когда у вас есть интерфейс, отображаемый в памяти для части аппаратного обеспечения, и вам нужно точно контролировать, куда указывают различные члены структуры. Это особенно не хорошая оптимизация скорости, так как большинство машин гораздо быстрее справляются с согласованными данными.источник
Он сообщает компилятору границы для выравнивания объектов в структуре. Например, если у меня есть что-то вроде:
На типичном 32-разрядном компьютере вы обычно «хотели бы» иметь 3 байта заполнения
a
иb
таким образом, чтобыb
он попадал на 4-байтовую границу, чтобы максимизировать скорость доступа (и это обычно происходит по умолчанию).Однако, если вам нужно соответствовать внешне определенной структуре, вы хотите убедиться, что компилятор разметит вашу структуру точно в соответствии с этим внешним определением. В этом случае вы можете дать компилятору
#pragma pack(1)
указание не вставлять какие-либо отступы между членами - если определение структуры включает заполнение между членами, вы вставляете его явно (например, обычно с именованными элементамиunusedN
илиignoreN
, или что-то в этом роде). порядок).источник
b
на 4-байтовой границе означает, что процессор может загрузить его, выдав одну 4-байтовую загрузку. Хотя это в некоторой степени зависит от процессора, но если он находится на нечетной границе, есть большая вероятность, что загрузка потребует от процессора выдачи двух отдельных инструкций загрузки, а затем используйте переключатель, чтобы собрать эти части вместе. Типичный штраф составляет порядка 3-кратной медленной загрузки этого предмета.Элементы данных (например, члены классов и структур) обычно выровнены по границам WORD или DWORD для процессоров текущего поколения, чтобы сократить время доступа. Для извлечения DWORD по адресу, который не делится на 4, требуется как минимум один дополнительный цикл ЦП на 32-разрядном процессоре. Так, если у вас есть, например, три члена char
char a, b, c;
, они на самом деле имеют тенденцию занимать 6 или 12 байт памяти.#pragma
позволяет вам переопределить это для достижения более эффективного использования пространства за счет скорости доступа или для согласованности хранимых данных между различными целями компилятора. Мне было очень весело с этим переходом с 16-битного на 32-битный код; Я ожидаю, что портирование на 64-битный код вызовет такие же головные боли для некоторого кода.источник
char a,b,c;
обычно занимает 3 или 4 байта памяти (по крайней мере, на x86), потому что их требование выравнивания составляет 1 байт. Если бы не было, как бы вы справилисьchar str[] = "foo";
? Доступ к achar
всегда является простой маской fetch-shift-mask, а доступ к aint
может быть fetch-fetch-merge или просто fetch, в зависимости от того, выровнен он или нет.int
имеет (на x86) 32-битное (4 байта) выравнивание, потому что в противном случае вы получите (скажем) половину отint
одногоDWORD
до половины от другого, и для этого потребуется два поиска.Компилятор может выравнивать элементы в структурах для достижения максимальной производительности на определенной платформе.
#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) для структур, которые должны передаваться по сети). Например, посмотрите следующий код и его последующий вывод:
Вывод выглядит следующим образом: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48
Обратите внимание , как размер структуру а именно то , что счетчик байт, но структура б имеет отступы добавили (см это подробно о кожухах). Делая это в отличие от пакета #pragma, вы можете контролировать преобразование «проводного формата» в соответствующие типы. Например, "char two [2]" в "short int" и так далее.
источник
sizeof
возвращает,size_t
который должен быть распечатан с использованием%zu
. Использование неправильного спецификатора формата вызывает неопределенное поведениеПочему один хочет использовать это?
Уменьшить память о структуре
Почему не стоит использовать это?
источник