Lorsque la programmation dans CI a trouvé inestimable de regrouper des structures en utilisant l' __attribute__((__packed__))
attribut GCCs , je peux donc facilement convertir un morceau structuré de mémoire volatile en un tableau d'octets à transmettre sur un bus, enregistré dans le stockage ou appliqué à un bloc de registres. Les structures empaquetées garantissent que lorsqu'elles sont traitées comme un tableau d'octets, elles ne contiennent aucun remplissage, ce qui est à la fois un gaspillage, un risque de sécurité possible et éventuellement incompatible avec le matériel d'interface.
N'y a-t-il pas de norme pour l'emballage des structures qui fonctionne dans tous les compilateurs C? Sinon, je suis une valeur aberrante en pensant que c'est une caractéristique critique pour la programmation des systèmes? Les premiers utilisateurs du langage C n'ont-ils pas trouvé le besoin de structurer les structures ou existe-t-il une sorte d'alternative?
la source
Réponses:
Dans une structure, ce qui compte, c'est le décalage de chaque membre par rapport à l'adresse de chaque instance de structure. Ce n'est pas tant la question de savoir comment les choses sont serrées.
Un tableau, cependant, compte dans la façon dont il est "emballé". La règle en C est que chaque élément du tableau est exactement N octets du précédent, où N est le nombre d'octets utilisés pour stocker ce type.
Mais avec une structure, il n'y a pas un tel besoin d'uniformité.
Voici un exemple d'un schéma d'emballage étrange:
Freescale (qui fabrique des microcontrôleurs automobiles) fabrique un micro doté d'un coprocesseur Time Processing Unit (google pour eTPU ou TPU). Il a deux tailles de données natives, 8 bits et 24 bits, et ne traite que des entiers.
Cette structure:
verra chaque U24 stocké son propre bloc 32 bits, mais uniquement dans la zone d'adresse la plus élevée.
Cette:
aura deux U24s adjacentes stockées dans 32 blocs de bits, et le U8 sera stocké dans le « trou » en face de la première U24,
elementA
.Mais vous pouvez dire au compilateur de tout emballer dans son propre bloc 32 bits, si vous le souhaitez; c'est plus cher sur la RAM mais utilise moins d'instructions pour les accès.
"emballage" ne signifie pas "emballer étroitement" - cela signifie simplement un schéma pour organiser les éléments d'une structure par rapport à l'offset.
Il n'y a pas de schéma générique, il dépend du compilateur + de l'architecture.
la source
struct b
pour se déplacerelementC
avant l'un des autres éléments, alors ce n'est pas un compilateur C conforme. Le réarrangement des éléments n'est pas autorisé dans CPuisque vous le mentionnez
__attribute__((__packed__))
, je suppose que votre intention est d'éliminer tout remplissage dans unstruct
(faire en sorte que chaque membre ait un alignement sur 1 octet).... Et la réponse est non". Le remplissage et l'alignement des données par rapport à une structure (et les tableaux contigus de structures en pile ou en tas) existent pour une raison importante. Sur de nombreuses machines, l'accès à la mémoire non aligné peut entraîner une pénalité de performance potentiellement importante (bien qu'elle devienne moindre sur certains matériels plus récents). Dans certains cas rares, un accès à la mémoire mal aligné entraîne une erreur de bus irrécupérable (peut même planter l'ensemble du système d'exploitation).
Étant donné que la norme C est axée sur la portabilité, il est peu logique de disposer d'un moyen standard pour éliminer tout le remplissage dans une structure et simplement permettre un désalignement des champs arbitraires, car cela risquerait potentiellement de rendre le code C non portable.
Le moyen le plus sûr et le plus portable de sortir ces données vers une source externe d'une manière qui élimine tout remplissage est de sérialiser vers / depuis les flux d'octets au lieu d'essayer simplement d'envoyer le contenu de la mémoire brute de votre
structs
. Cela empêche également votre programme de subir des pénalités de performances en dehors de ce contexte de sérialisation, et vous permettra également d'ajouter librement de nouveaux champs à unstruct
sans supprimer et perturber le logiciel entier. Cela vous donnera également de la place pour lutter contre l'endianité et des choses comme ça si cela devient un problème.Il existe un moyen d'éliminer tout remplissage sans atteindre les directives spécifiques au compilateur, bien qu'il ne soit applicable que si l'ordre relatif entre les champs n'a pas d'importance. Étant donné quelque chose comme ça:
... nous avons besoin du remplissage pour un accès mémoire aligné par rapport à l'adresse de la structure contenant ces champs, comme ceci:
... où
.
indique un rembourrage. Chacunx
doit s'aligner sur une limite de 8 octets pour les performances (et parfois même un comportement correct).Vous pouvez éliminer le remplissage de manière portable en utilisant une représentation SoA (structure de tableau) comme ceci (supposons que nous ayons besoin de 8
Foo
instances):Nous avons effectivement démoli la structure. Dans ce cas, la représentation de la mémoire devient comme ceci:
... et ça:
... plus de surcharge de remplissage, et sans impliquer un accès à la mémoire mal aligné puisque nous n'accédons plus à ces champs de données comme décalage d'une adresse de structure, mais plutôt comme décalage d'une adresse de base pour ce qui est effectivement un tableau.
Cela présente également l'avantage d'être plus rapide pour l'accès séquentiel en raison de la réduction de la consommation de données (plus de remplissage non pertinent dans le mélange pour ralentir le taux de consommation de données pertinent de la machine) et également du potentiel pour le compilateur de vectoriser le traitement de manière très triviale. .
L'inconvénient est que c'est un PITA à coder. Il est également potentiellement moins efficace pour l'accès aléatoire avec une plus grande foulée entre les champs, où souvent les représentants AoS ou AoSoA feront mieux. Mais c'est une façon standard d'éliminer le rembourrage et d'emballer les choses aussi étroitement que possible sans visser avec l'alignement de tout.
la source
Toutes les architectures ne sont pas identiques, activez simplement l'option 32 bits sur un module et voyez ce qui se passe lorsque vous utilisez le même code source et le même compilateur. L'ordre des octets est une autre limitation bien connue. Ajoutez une représentation en virgule flottante et les problèmes s'aggravent. L'utilisation de Packing pour envoyer des données binaires n'est pas portable. Pour le normaliser afin qu'il soit pratiquement utilisable, vous devrez redéfinir la spécification du langage C.
Bien que courant, utiliser Pack pour envoyer des données binaires est une mauvaise idée si vous voulez la sécurité, la portabilité ou la longévité des données. À quelle fréquence lisez-vous un blob binaire à partir d'une source dans votre programme. À quelle fréquence vérifiez-vous que toutes les valeurs sont valables, qu'un pirate ou un changement de programme n'a pas «obtenu» les données? Au moment où vous avez codé une routine de vérification, vous pourriez aussi bien utiliser des routines d'importation et d'exportation.
la source
Une alternative très courante est le "padding nommé":
Cela ne suppose la structure ne sera pas rembourré à 8 octets.
la source