Je me demandais si quelqu'un pourrait m'expliquer ce que fait la #pragma pack
dé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ù.
c
c-preprocessor
pragma-pack
Cenoc
la source
la source
#pragma
directives, elles sont définies par l'implémentation.A mod s = 0
où 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.Réponses:
#pragma pack
indique 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:Le compilateur peut choisir de mettre la structure en mémoire comme ceci:
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:Et
sizeof(Test)
serait 1 × 6 = 6.Avec
#pragma pack(2)
, la structure ci-dessus serait présentée comme suit:Et
sizeof(Test)
serait 2 × 4 = 8.L'ordre des variables dans la structure est également important. Avec des variables ordonnées comme suit:
et avec
#pragma pack(2)
, la structure serait présentée comme ceci:et
sizeOf(Test)
serait 3 × 2 = 6.la source
#pragma
est 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 pack
est 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.la source
Il indique au compilateur la limite à laquelle aligner les objets d'une structure. Par exemple, si j'ai quelque chose comme:
Avec une machine 32 bits typique, vous voudriez normalement avoir 3 octets de remplissage entre
a
etb
ainsi celab
atterrira à 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ésunusedN
ouignoreN
, ou quelque chose sur ordre).la source
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.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.#pragma
vous 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.la source
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-vouschar str[] = "foo";
? L'accès à achar
est toujours un simple masque de fetch-shift, tandis que l'accès à unint
peut être fetch-fetch-merge ou simplement fetch, selon qu'il est aligné ou non.int
a (sur x86) un alignement de 32 bits (4 octets) car sinon vous obtiendriez (disons) la moitié d'unint
dans unDWORD
et la moitié dans l'autre, et cela prendrait deux recherches.Le compilateur peut aligner les membres dans les structures pour obtenir des performances maximales sur la plate-forme définie.
#pragma pack
La 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 1
pour exclure tout alignement indésirable.la source
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.
la source
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.
la source
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.
la source
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
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:
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.
la source
sizeof
renvoie unsize_t
qui doit être imprimé en utilisant%zu
. Utiliser le mauvais spécificateur de format appelle un comportement indéfiniPourquoi veut-on l'utiliser?
Réduire la mémoire de la structure
Pourquoi ne devrait-on pas l'utiliser?
la source