Bonjour,
Je suis entrain d'implémenter une communication entre deux programmes sur un réseau. La communication utilise un protocole au format binaire. Je suis entrain de me poser quelques questions sur l'alignement des structure et surtout l'espacement que mettent les compilateurs pour respecter l'alignement. Voici un exemple de structure :
struct hello {
uint8_t version;
uint16_t id;
uint32_t name;
};
Les membres ont été volontairement arrangé pour que le compilateur ajoute des espacements. Si j'ai bien compris comment le compilateur est sensé modifier la structure, voici ce que cela donnerait :
struct hello {
uint8_t version;
/* L'élément suivant fait deux octets, il faut donc l'aligner sur une adresse
* multiple de deux. */
char pad1[1];
uint16_t id;
/* L'élément suivant fait quatre octets, il faut donc l'aligner sur une adresse
* multiple de quatre. */
char pad2[2];
uint32_t name;
};
Du coup, je me demande, est-ce-que je peux être sur que se sera toujours les mêmes espacements qui seront placés ? Sur n'importe-quelle architecture, avec n'importe-quel compilateur ? Si oui, je peux donc envoyer directement la structure sur le réseau, après avoir pris soin de modifier le boutisme ?
J'en profite pour poser une autre question : l'alignement est-il un problème unique aux développeurs noyaux/hardware, ou il concerne tout type de programmes C ?
Merci pour vos réponse :)
# Normalement tu n'as aucune garantie
Posté par Renault (site web personnel) . Évalué à 3.
Le compilateur va faire un alignement mémoire de tes éléments de structure pour coller avec l'adressage de ton processeur. Donc de fait, selon la capacité d'adressage du processeur considéré l'alignement mémoire ne sera pas identique. Comme tu sembles te préoccuper du boutisme, c'est bien que tu comptes dialoguer avec des processeurs potentiellement non X86.
Note que sur le réseau, le boutisme standard est le grand boutisme. Ce qui est contraire à l'architecture dominant de nos ordinateurs de bureau.
Après je ne me souviens plus de la norme exacte concernant le soin laissé aux compilateurs concernant les bits de bourrage, de mémoire tu ne peux faire une généralité sur le comportement des compilateurs à ce sujet surtout sur des architectures différentes.
L'alignement est un problème que tu peux rencontrer dans tout programme C potentiellement, mais il est vrai que généralement tu ne t'en préoccupes pas surtout si tu enregistres les valeurs champ par champ (méthode conseillée pour éviter les problèmes avec les structures).
[^] # Re: Normalement tu n'as aucune garantie
Posté par needs . Évalué à 1.
Merci bien :)
Une dernière petite question: est-ce-que travailler avec des
uint*_t
en mémoire plutôt que les classiquesint
,long
est moins performant ?J'imagine que non, mais bon, je ne connais pas encore tout les détails de ce genre de choses :)
[^] # Re: Normalement tu n'as aucune garantie
Posté par freem . Évalué à 2.
Oui et non. Déjà, parce que performance, ça veut rien dire: performance en calcul, ou en place?
Aussi parce que sur les plates-formes ou uint32_t ( par exemple ) est un define du int, non, par exemple, mais ça peut varier.
Si la performance à ce niveau t'intéresse tant, je te conseille une petite lecture du fichier stdint.h. Plus précisément:
Donc, voila: en fonction de tes besoins, tu as des types différents.
Sinon, j'ai noté l'utilisation d'un char, au milieu de types stdint, pourquoi ne pas avoir utilisé uint8_t? Ça me semblerait plus cohérent.
Enfin, si tu ne veux pas utiliser packed mais que tu veux contrôler l'alignement, il existe de mémoire une syntaxe pour les bitfields en C. Je ne m'en souviens plus, c'est pas le genre de trucs que j'utilise souvent, mais c'est simple à retrouver.
Bon, je mentionne surtout ça parce que tu risques d'y être confronté, et que c'est parfois intéressant si tu sais que tel variable est plus petite qu'un octet et que tu cherches une performance de taille maximale sans avoir à compresser par algo. Dans ton usage précis par contre, je ne pense pas que ce soit une solution acceptable.
# packed
Posté par ymorin . Évalué à 5.
Comme dit précédemment, il n'y a par défaut aucune garantie.
Par contre, tu peux utiliser le mot-clé
packed
pour que le compilateur ne rajoute pas de 'blanc' entre les membres de ta structure.Et bien sûr, les dispositions habituelles grand-boutisme / petit-boutisme. ;-)
Hop,
Moi.
[^] # Re: packed
Posté par needs . Évalué à 0.
Pour
packed
je suis pas trop pour, c'est pas très portable et cela pose des problèmes de performances non ? Je préfère avoir une structure non 'packée' sur laquelle je peux travailler et 'packer' manuellement au moment de l'envoi sur le réseau.# Abstraction
Posté par mrlem (site web personnel) . Évalué à 1.
Le padding peut effectivement varier d'une archi à l'autre, d'un compilo à l'autre. Cumulé avec toutes les autres petites variations subtiles (e.g. boutisme, …), c'est l'une des raisons pour lesquelles il y a eu l'émergence de technos genre ASN.1 pour décrire de manière plus formelle et indépendante de la plateforme des structures de données complexes.
[^] # Re: Abstraction
Posté par mrlem (site web personnel) . Évalué à 2.
Ah, et pour :
C'est utile dès que ton application "exporte" une structure qui sera utilisée ailleurs : dans la majorité des cas ce sera donc quand tu veux communiquer la structure via un réseau ou via un fichier, sans pouvoir présumer de la machine/os/application à la réception. Ce n'est donc pas réservé au noyau.
Comme mentionné dans d'autres commentaires, on peut également s'y intéresser pour des problématiques de perf, pour des parties critiques de code (notamment, mais pas seulement, dans le noyau).
# Non garantie du compilateur
Posté par belzo . Évalué à -1.
Attention à plusieurs choses:
1- selon votre machine, surtout si vous avez des machines <32bits (même rarement ces temps ci) vous pouvez aussi jouer avec les mots clés du compilo (si dispo) du genre aligned ou packed (voir https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html)
2- il se peut aussi que le compilateur casse l'ordre des champ de la structure pour réorganiser par taille. Si vous voulez maitriser votre structure, il est de bon usage de toujours spécifier vos champs par ordre décroissant de taille.
3- par expérience, sauf besoin spécifiques, il faut se passer un maximum des types uintXX_t. n'utiliser que des types primitifs (char, int, long, …) suffit largement et permet surtout de s'affranchir de la machine cible. Par contre, si l'on doit faire communiquer 2 machines différents (ex 32 et 64 bits) les typer uintXX_t sont pratique et ne sont que des macros sur les types primitifs de la machines (char, int, long, …)
4- pour maitriser un padding implicite du compilo on peut aussi faire un padding explicite dans la structure que nous voulons.
[^] # Commentaire supprimé
Posté par Anonyme . Évalué à 2.
Ce commentaire a été supprimé par l’équipe de modération.
[^] # Re: Non garantie du compilateur
Posté par freem . Évalué à 1.
Je serais intéressé de savoir quels problèmes tu as eu avec ces types? Mis à part qu'ils peuvent ne pas être définis sur une archi ( contrairement aux types least et fast ), je ne vois pas de problème particulier?
En fait, j'ai l'impression que c'est même le contraire, dans un code:
voir des constantes numériques faites main pour tester la valeur maxi lors d'une itération n'est pas rare. Et bien sûr, la portabilité prend un sacré coup dans la gueule ( voire même la stabilité s'il s'est planté, mais bon… ). Alors que des types XintYY_t, quitte à ce qu'ils soient redéfinis ( comme le fait la SDL 1.2, par exemple ) peuvent être comparés au premier coup d'œil à la valeur testée: 0xFFFF et 0x7FFF pour les signés, -1 pour les non signés ( ça au moins ça marche toujours… je crois? ).
Ce que je veux dire, c'est qu'au moins, avec un uint16_t les valeurs mini/maxi/maxi non signé numériques sont fiables ( même si je suis d'accord qu'il faut éviter, mais les codes sources n'ont pas tous la possibilité d'être compilé avec des compilos respectant C99 ( 15 ans c'est jeune comparé au langage C ).
# Erreur d'analyse
Posté par -=[ silmaril ]=- (site web personnel) . Évalué à 2.
Au vu de ta structure, la représentation mémoire ne nécessite qu'un padding d'un octet, en effet le padding de 8bits pour id suffit à aligner name sur un 32bits:
Pour savoir ce que le compilo (gcc en tout cas) fait: -Wpadded est utile
[^] # Re: Erreur d'analyse
Posté par needs . Évalué à 1.
Effectivement, bien vu !
# Utiliser un standard / de l'existant
Posté par lolop (site web personnel) . Évalué à 3.
Salut, tu va réinventer ce qui a déjà été fait par d'autres qui ont traité les différents cas en ayant une base d'architectures / compilos (et donc problèmes potentiels) bien plus large que toi.
Sans aller jusqu'à CORBA et la définition d'interfaces IDL, tu pourrais regarder la définition de messages ONC RPC, ou plus récent le protobuf de Google.
A+, bons développements.
Votez les 30 juin et 7 juillet, en connaissance de cause. http://www.pointal.net/VotesDeputesRN
# le mur
Posté par steph1978 . Évalué à 3.
je te suspecte de vouloir sérialiser des struct à la main.
ça va faire mal :)
je rejoints le commentaire de lolop : ne réinvente pas le roue, utilise protobuf ou autre.
[^] # Re: le mur
Posté par needs . Évalué à 2.
Haha, j'utilise le préprocesseur pour générer le code qui sérialise les structures. Un genre de X macros mais poussé un peu plus loin que la simple génération d'énumération.
J'ai un fichier qui contient ceci:
Et par exemple je génère la structure comme ceci:
J'utilise ce trick à mainte reprise pour générer les fonctions qui sérialisent les structures, etc… Bon la je le montre de façon simple, mais l'ensemble est beaucoup plus complet et permet de sérialiser plein de choses :)
[^] # Re: le mur
Posté par lolop (site web personnel) . Évalué à 3.
Et tu as essayé ça sur combien de compilateurs, avec des petits et gros boutiens, avec des archis 16, 32, 64 bits, et chacun communique avec les autres sans problème ?
Votez les 30 juin et 7 juillet, en connaissance de cause. http://www.pointal.net/VotesDeputesRN
[^] # Re: le mur
Posté par needs . Évalué à 1.
Pour l'instant uniquement 32 bit 64 bit x86 avec
clang
ougcc
. Et tous communiquent sans problèmes.Mais je me fait trop de soucis, les seules fonctions que j'utilise sont celles de la famille
ntohl()
! Bon du coups il faudrait que je voie avec une architecture dont le boutisme est différent de celui du x86. Et cela sera fait, et si problème il y a, il sera rapidement repéré car j'ai pas mal de tests qui concernent uniquement le protocole et les communications sur le réseau :)Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.