Je me demandais si quelqu'un pourrait m'expliquer ce que fait l'instruction __processor #pragma pack
et, plus important encore, pourquoi on voudrait l'utiliser.
J'ai consulté la page MSDN , qui offrait un aperçu, mais j'espérais entendre davantage de personnes expérimentées. Je l'ai déjà vu dans le code auparavant, bien que je n'arrive pas à trouver où.
#pragma pack
indique au compilateur d'empaqueter les membres de la structure avec un alignement particulier. Lorsque vous déclarez une structure, la plupart des compilateurs insèrent un remplissage entre les membres pour garantir leur alignement sur les adresses appropriées en mémoire (généralement un multiple de la taille du type). Cela évite une dégradation des performances (ou une erreur pure et simple) sur certaines architectures associées à l'accès à des variables non alignées correctement. Par exemple, des entiers de 4 octets et la structure suivante:
struct Test
{
char AA;
int BB;
char CC;
};
Le compilateur pourrait 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 le #pragma
(à ma connaissance) concerne les périphériques matériels dans lesquels 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 agencé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 agencé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.
#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. Désactiver des avertissements spécifiques est particulièrement utile si vous compilez avec les avertissements en tant qu'indicateur d'erreurs activé.
#pragma pack
est spécifiquement utilisé pour indiquer que la structure en train d'être compressée ne doit pas avoir ses membres alignés. C'est utile lorsque vous avez une interface mappée en mémoire sur un matériel et que vous devez pouvoir contrôler exactement où les différents membres de la structure pointent. Il ne s’agit notamment pas d’une bonne optimisation de la vitesse, car la plupart des machines traitent beaucoup plus rapidement les données alignées.
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 a
et b
afin que b
atterrit à une limite de 4 octets afin de maximiser sa vitesse d'accès (et c'est ce qui se passera généralement par défaut ).
Si, toutefois, vous devez faire correspondre une structure définie en externe, vous voulez vous assurer que le compilateur présente votre structure exactement en fonction de cette définition externe. Dans ce cas, vous pouvez donner au compilateur une #pragma pack(1)
pour lui dire non d'insérer un remplissage entre les membres - si la définition de la structure inclut un remplissage entre les membres, vous l'insérez explicitement (par exemple, avec des membres nommés unusedN
ou ignoreN
, ou quelque chose dans cet ordre).
Les éléments de données (par exemple, les membres de classes et de structures) sont généralement alignés sur des limites 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 non divisible par 4 nécessite au moins un cycle de processeur supplémentaire sur un processeur 32 bits. Donc, si vous avez par exemple char a, b, c;
, ils ont tendance à prendre 6 ou 12 octets de stockage.
#pragma
vous permet de remplacer cette valeur pour optimiser l'utilisation de l'espace, au détriment de la vitesse d'accès ou de la cohérence des données stockées entre 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 le même type de problèmes pour certains codes.
Un compilateur peut / place des membres de la structure sur des limites d’octets particulières pour des raisons de performance sur une architecture particulière. Cela peut laisser un remplissage inutilisé entre les membres. Structure d'emballage oblige les membres à être contigus.
Cela peut être important, par exemple, si vous souhaitez qu'une structure se conforme à un fichier ou à un format de communication particulier, dans lequel les données dont vous avez besoin doivent se trouver à des positions spécifiques dans une séquence. Cependant, un tel usage ne traite pas les problèmes d’endurance, il est donc possible qu’il ne soit pas portable.
Vous pouvez également superposer exactement la structure de registre interne de certains périphériques d'E/S, tels qu'un contrôleur UART ou un contrôleur USB, par exemple, afin que l'accès au registre s'effectue via une structure plutôt que des adresses directes.
Le compilateur peut aligner les membres dans les structures pour obtenir des performances maximales sur certaines plates-formes. La directive #pragma pack
vous permet de contrôler cet alignement. Habituellement, vous devriez 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 non souhaité.
Vous ne voudriez probablement utiliser ceci 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 semble être un outil assez grossier pour atteindre cet objectif. Une meilleure approche consisterait à coder un mini-pilote dans assembler et à lui attribuer une interface d’appel en C plutôt que de tâtonner avec ce pragma.
Je l'ai déjà utilisé dans le code auparavant, bien que pour me connecter au 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, Carbon (qui était elle-même rétrocompatible avec la version originale de M68k System 6.5 ... vous voyez l’idée). Les fichiers de préférences de la version d'origine étaient une copie binaire d'une structure de configuration, qui utilisait la fonction #pragma pack(1)
pour éviter de prendre de l'espace supplémentaire et de sauvegarder des fichiers indésirables (c'est-à-dire les octets de remplissage qui seraient sinon dans la structure).
Les auteurs originaux du code avaient également utilisé #pragma pack(1)
pour stocker les structures utilisées comme messages dans les communications inter-processus. Je pense que la raison ici était d'éviter la possibilité de tailles de remplissage inconnues ou modifiées, car le code examinait parfois une partie spécifique de la structure du message en comptant un nombre d'octets depuis le début (ewww).
J'ai vu des gens l'utiliser pour s'assurer qu'une structure prend toute une ligne de cache afin d'empêcher tout faux partage dans un contexte multithread. Si vous allez avoir un grand nombre d'objets qui seront emballés par défaut de manière lâche, cela pourrait économiser de la mémoire et améliorer les performances du cache afin de les compresser davantage, bien qu'un accès mémoire non aligné ralentisse généralement les choses, ce qui peut poser problème.
Notez que #pragma pack offre d'autres moyens d'assurer la cohérence des données (par exemple, certaines personnes utilisent #pragma pack (1) pour les structures à envoyer sur le réseau). Par exemple, voir 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));
}
Le résultat est le suivant: Sizeof (struct a): 15, sizeof (struct b): 24 Sizeof (deux): 30, sizeof (twob): 48
Notez que la taille de struct a correspond exactement au nombre d'octets, mais que des modifications sont ajoutées à la struct b (voir this pour plus de détails sur le remplissage). En faisant cela, par opposition au pack #pragma, vous pouvez contrôler la conversion du "format filaire" en types appropriés. Par exemple, "char two [2]" dans un "short int" et cetera.