#pragma упаковывают эффект

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

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

210
задан BattleTested 22 August 2018 в 00:05
поделиться

9 ответов

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

388
ответ дан 23 November 2019 в 04:35
поделиться

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

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

24
ответ дан 23 November 2019 в 04:35
поделиться

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

struct foo { 
    char a;
    int b;
};

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

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

15
ответ дан 23 November 2019 в 04:35
поделиться

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

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

7
ответ дан 23 November 2019 в 04:35
поделиться

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

2
ответ дан 23 November 2019 в 04:35
поделиться

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

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

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

2
ответ дан 23 November 2019 в 04:35
поделиться

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

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

1
ответ дан 23 November 2019 в 04:35
поделиться

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

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

1
ответ дан 23 November 2019 в 04:35
поделиться

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

1
ответ дан 23 November 2019 в 04:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: