#pragma pack effect

233

Je me demandais si quelqu'un pourrait m'expliquer ce que fait la #pragma packdéclaration du préprocesseur et, plus important encore, pourquoi on voudrait l'utiliser.

J'ai vérifié la page MSDN , qui offrait un aperçu, mais j'espérais en savoir plus de personnes ayant de l'expérience. Je l'ai déjà vu dans le code, bien que je n'arrive plus à trouver où.

Cenoc
la source
1
Il force un alignement / compactage particulier d'une structure, mais comme toutes les #pragmadirectives, elles sont définies par l'implémentation.
dreamlax
A mod s = 0où A est l'adresse et s est la taille du type de données; cela vérifie si une donnée n'est pas mal alignée.
legends2k

Réponses:

422

#pragma packindique au compilateur de regrouper les membres de la structure avec un alignement particulier. La plupart des compilateurs, lorsque vous déclarez une structure, insèrent un remplissage entre les membres pour garantir qu'ils sont alignés sur les adresses appropriées en mémoire (généralement un multiple de la taille du type). Cela évite la dégradation des performances (ou une erreur pure et simple) sur certaines architectures associées à l'accès à des variables qui ne sont pas correctement alignées. Par exemple, étant donné des entiers de 4 octets et la structure suivante:

struct Test
{
   char AA;
   int BB;
   char CC;
};

Le compilateur peut choisir de mettre la structure en mémoire comme ceci:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

et sizeof(Test)serait 4 × 3 = 12, même s'il ne contient que 6 octets de données. Le cas d'utilisation le plus courant pour #pragma(à ma connaissance) est lorsque vous travaillez avec des périphériques matériels où vous devez vous assurer que le compilateur n'insère pas de remplissage dans les données et que chaque membre suit le précédent. Avec #pragma pack(1), la structure ci-dessus serait présentée comme suit:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

Et sizeof(Test)serait 1 × 6 = 6.

Avec #pragma pack(2), la structure ci-dessus serait présentée comme suit:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

Et sizeof(Test)serait 2 × 4 = 8.

L'ordre des variables dans la structure est également important. Avec des variables ordonnées comme suit:

struct Test
{
   char AA;
   char CC;
   int BB;
};

et avec #pragma pack(2), la structure serait présentée comme ceci:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

et sizeOf(Test)serait 3 × 2 = 6.

Nick Meyer
la source
76
Il pourrait être utile d'ajouter les inconvénients de l'emballage. (les accès aux objets non alignés sont lents dans le meilleur des cas, mais provoqueront des erreurs sur certaines plateformes.)
jalf
11
Il semble que la "pénalité de performance" des alignements mentionnée pourrait en fait être un avantage sur certains systèmes danluu.com/3c-conflict .
6
@Pacerier Pas vraiment. Ce post parle d'un alignement assez extrême (alignement sur les limites de 4 Ko). Le CPU attend certains alignements minimaux pour divers types de données, mais ceux-ci nécessitent, dans le pire des cas, un alignement sur 8 octets (sans compter les types de vecteurs qui peuvent nécessiter un alignement sur 16 ou 32 octets). Ne pas s'aligner sur ces limites vous donne généralement un impact notable sur les performances (car une charge peut devoir être effectuée en deux opérations au lieu d'une), mais le type est bien aligné ou il ne l'est pas. Un alignement plus strict que cela ne vous
rapporte
6
En d'autres termes, un double s'attend à être sur une limite de 8 octets. Le placer sur une limite de 7 octets nuira aux performances. Mais le mettre sur une limite de 16, 32, 64 ou 4096 octets ne vous achète rien au-dessus de ce que la limite de 8 octets vous a déjà donné. Vous obtiendrez les mêmes performances du processeur, tout en obtenant une utilisation du cache bien pire pour les raisons décrites dans cet article.
jalf
4
Ainsi , la leçon n'est pas « l' emballage est bénéfique » (emballage viole l' alignement naturel des types, de sorte que nuit à la performance), mais simplement « ne pas trop align au - delà de ce qui est nécessaire »
jalf
27

#pragmaest utilisé pour envoyer des messages non portables (comme dans ce compilateur uniquement) au compilateur. Des choses comme la désactivation de certains avertissements et des structures d'emballage sont des raisons courantes. La désactivation d'avertissements spécifiques est particulièrement utile si vous compilez avec les avertissements lorsque l'indicateur d'erreurs est activé.

#pragma packest spécifiquement utilisé pour indiquer que la structure en cours de compression ne doit pas avoir ses membres alignés. C'est utile lorsque vous avez une interface mappée en mémoire avec un élément matériel et que vous devez pouvoir contrôler exactement où les différents membres de la structure pointent. Ce n'est notamment pas une bonne optimisation de la vitesse, car la plupart des machines sont beaucoup plus rapides à traiter les données alignées.

nmichaels
la source
17
Pour annuler, procédez comme suit: #pragma pack (push, 1) et #pragma pack (pop)
malhal
16

Il indique au compilateur la limite à laquelle aligner les objets d'une structure. Par exemple, si j'ai quelque chose comme:

struct foo { 
    char a;
    int b;
};

Avec une machine 32 bits typique, vous voudriez normalement avoir 3 octets de remplissage entre aet bainsi cela batterrira à une limite de 4 octets pour maximiser sa vitesse d'accès (et c'est ce qui se produira généralement par défaut).

Si, cependant, vous devez faire correspondre une structure définie en externe, vous voulez vous assurer que le compilateur présente votre structure exactement selon cette définition externe. Dans ce cas, vous pouvez donner au compilateur un #pragma pack(1)pour lui dire de ne pas insérer de remplissage entre les membres - si la définition de la structure inclut le remplissage entre les membres, vous l'insérez explicitement (par exemple, généralement avec des membres nommés unusedNou ignoreN, ou quelque chose sur ordre).

Jerry Coffin
la source
"vous" voudriez "normalement avoir 3 octets de remplissage entre a et b pour que b atterrisse à une limite de 4 octets pour maximiser sa vitesse d'accès" - comment le fait d'avoir 3 octets de remplissage maximisera-t-il la vitesse d'accès?
Ashwin
8
@Ashwin: le placement bà une limite de 4 octets signifie que le processeur peut le charger en émettant une seule charge de 4 octets. Bien que cela dépende quelque peu du processeur, s'il se trouve à une limite étrange, il y a de fortes chances que son chargement nécessite que le processeur émette deux instructions de chargement distinctes, puis utilisez un sélecteur pour assembler ces pièces. La pénalité typique est de l'ordre d'une charge 3x plus lente de cet article.
Jerry Coffin
... si vous regardez le code assembleur pour lire les int alignés et non alignés, la lecture alignée est généralement un simple mnémonique. La lecture non alignée peut être composée de 10 lignes d'assemblage facilement car elle assemble l'int.
SF.
2
@SF .: Cela peut être - mais même quand ce n'est pas le cas, ne vous y trompez pas - sur un processeur x86 (pour un exemple évident), les opérations sont effectuées sur le matériel, mais vous obtenez toujours à peu près le même ensemble d'opérations et ralentissement.
Jerry Coffin du
8

Les éléments de données (par exemple, les membres des classes et des structures) sont généralement alignés sur les frontières WORD ou DWORD pour les processeurs de génération actuels afin d'améliorer les temps d'accès. La récupération d'un DWORD à une adresse qui n'est pas divisible par 4 nécessite au moins un cycle CPU supplémentaire sur un processeur 32 bits. Donc, si vous avez par exemple trois membres char char a, b, c;, ils ont en fait tendance à prendre 6 ou 12 octets de stockage.

#pragmavous permet de remplacer cela pour obtenir une utilisation de l'espace plus efficace, au détriment de la vitesse d'accès ou pour la cohérence des données stockées entre les différentes cibles du compilateur. Je me suis beaucoup amusé avec cette transition du code 16 bits au code 32 bits; Je m'attends à ce que le portage en code 64 bits provoque les mêmes types de maux de tête pour certains codes.

Pontus Gagge
la source
En fait, char a,b,c;cela prend généralement 3 ou 4 octets de stockage (sur x86 au moins) - c'est parce que leur exigence d'alignement est de 1 octet. Si ce n'était pas le cas, comment réagiriez-vous char str[] = "foo";? L'accès à a charest toujours un simple masque de fetch-shift, tandis que l'accès à un intpeut être fetch-fetch-merge ou simplement fetch, selon qu'il est aligné ou non. inta (sur x86) un alignement de 32 bits (4 octets) car sinon vous obtiendriez (disons) la moitié d'un intdans un DWORDet la moitié dans l'autre, et cela prendrait deux recherches.
Tim Čas
3

Le compilateur peut aligner les membres dans les structures pour obtenir des performances maximales sur la plate-forme définie. #pragma packLa directive vous permet de contrôler cet alignement. Habituellement, vous devez le laisser par défaut pour des performances optimales. Si vous devez transmettre une structure à la machine distante, vous utiliserez généralement #pragma pack 1pour exclure tout alignement indésirable.

Kirill V. Lyadvinsky
la source
2

Un compilateur peut placer des membres de structure sur des limites d'octets particulières pour des raisons de performances sur une architecture particulière. Cela peut laisser un remplissage inutilisé entre les membres. L'emballage de structure oblige les membres à être contigus.

Cela peut être important, par exemple, si vous avez besoin d'une structure pour se conformer à un format de fichier ou de communication particulier où les données dont vous avez besoin se trouvent à des positions spécifiques dans une séquence. Cependant, une telle utilisation ne traite pas des problèmes d'endianité, donc bien qu'utilisée, elle peut ne pas être portable.

Il peut également superposer exactement la structure de registre interne de certains périphériques d'E / S tels qu'un contrôleur UART ou USB par exemple, afin que l'accès au registre se fasse via une structure plutôt que des adresses directes.

Clifford
la source
2

J'ai vu des gens l'utiliser pour s'assurer qu'une structure prend toute une ligne de cache pour éviter un faux partage dans un contexte multithread. Si vous prévoyez d'avoir un grand nombre d'objets qui seront compressés de manière lâche par défaut, cela pourrait économiser de la mémoire et améliorer les performances du cache pour les compresser plus étroitement, bien que l'accès à la mémoire non alignée ralentisse généralement les choses, ce qui peut entraîner un inconvénient.

stonemetal
la source
1

Vous ne voudrez probablement l'utiliser que si vous codiez sur un matériel (par exemple, un périphérique mappé en mémoire) qui avait des exigences strictes pour la commande et l'alignement des registres.

Cependant, cela ressemble à un outil assez contondant pour atteindre cet objectif. Une meilleure approche serait de coder un mini-pilote en assembleur et de lui donner une interface d'appel C plutôt que de tâtonner avec ce pragma.

msw
la source
En fait, je l'utilise beaucoup pour économiser de l'espace dans de grandes tables qui ne sont pas consultées fréquemment. Là, c'est uniquement pour économiser de l'espace et non pour un alignement strict. (Je viens de vous voter, au fait. Quelqu'un vous a donné un vote négatif.)
Todd Lehman
1

Je l'ai déjà utilisé dans du code, mais uniquement pour interfacer avec du code hérité. Il s'agissait d'une application Mac OS X Cocoa qui devait charger des fichiers de préférences à partir d'une version antérieure de Carbon (qui était elle-même rétrocompatible avec la version originale du système M68k 6.5 ... vous avez l'idée). Les fichiers de préférences dans la version originale étaient un vidage binaire d'une structure de configuration, qui utilisait le #pragma pack(1)pour éviter de prendre de l'espace supplémentaire et d'économiser du courrier indésirable (c'est-à-dire les octets de remplissage qui seraient autrement dans la structure).

Les auteurs originaux du code avaient également utilisé #pragma pack(1)pour stocker des structures qui étaient utilisées comme messages dans la communication inter-processus. Je pense que la raison ici était d'éviter la possibilité de tailles de remplissage inconnues ou modifiées, car le code regardait parfois une partie spécifique de la structure du message en comptant un certain nombre d'octets depuis le début (ewww).


la source
0

Notez qu'il existe d'autres façons d'assurer la cohérence des données offertes par le pack #pragma (par exemple, certaines personnes utilisent le pack #pragma (1) pour les structures qui doivent être envoyées sur le réseau). Par exemple, consultez le code suivant et sa sortie suivante:

#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));
}

La sortie est la suivante: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48

Remarquez comment la taille de la structure a est exactement ce que le nombre d'octets est, mais la structure b a un remplissage ajouté (voir ceci pour plus de détails sur le remplissage). En faisant cela, contrairement au pack #pragma, vous pouvez contrôler la conversion du "format de fil" en types appropriés. Par exemple, "char deux [2]" dans un "short int" et cetera.

wangchow
la source
Non c'est mal. Si vous regardez la position en mémoire de b.two, ce n'est pas un octet après b.one (le compilateur peut (et souvent) alignera b.two pour qu'il soit aligné sur l'accès aux mots). Pour a.two, c'est exactement un octet après a.one. Si vous avez besoin d'accéder à a.two comme un int court, vous devriez avoir 2 alternatives, soit utiliser une union (mais cela échoue généralement si vous avez un problème d'endianité), ou décompresser / convertir par code (en utilisant la fonction ntohX appropriée)
xryl669
1
sizeofrenvoie un size_tqui doit être imprimé en utilisant%zu . Utiliser le mauvais spécificateur de format appelle un comportement indéfini
phuclv
0

Pourquoi veut-on l'utiliser?

Réduire la mémoire de la structure

Pourquoi ne devrait-on pas l'utiliser?

  1. Cela peut entraîner une baisse des performances, car certains systèmes fonctionnent mieux sur les données alignées
  2. Une machine ne parviendra pas à lire les données non alignées
  3. Le code n'est pas portable
VINOTH ENERGETIC
la source