61 votes

Comment organiser les membres dans une structure afin de gaspiller le moins d'espace possible pour l'alignement ?

[Ne pas dupliquer Rembourrage et emballage de la structure . Cette question porte sur la manière et le moment où le rembourrage se produit. Celle-ci porte sur la manière de le gérer].

Je viens de réaliser combien de mémoire est gaspillée à cause de l'alignement en C++. Considérons l'exemple simple suivant :

struct X
{
    int a;
    double b;
    int c;
};

int main()
{
    cout << "sizeof(int) = "                      << sizeof(int)                      << '\n';
    cout << "sizeof(double) = "                   << sizeof(double)                   << '\n';
    cout << "2 * sizeof(int) + sizeof(double) = " << 2 * sizeof(int) + sizeof(double) << '\n';
    cout << "but sizeof(X) = "                    << sizeof(X)                        << '\n';
}

En utilisant g++, le programme donne la sortie suivante :

sizeof(int) = 4
sizeof(double) = 8
2 * sizeof(int) + sizeof(double) = 16
but sizeof(X) = 24

Cela représente une surcharge de mémoire de 50 % ! Dans une matrice de 3 gigaoctets de 134'217'728 X d'un gigaoctet serait du pur rembourrage.

Heureusement, la solution au problème est très simple - il suffit d'échanger double b y int c autour :

struct X
{
    int a;
    int c;
    double b;
};

Maintenant, le résultat est beaucoup plus satisfaisant :

sizeof(int) = 4
sizeof(double) = 8
2 * sizeof(int) + sizeof(double) = 16
but sizeof(X) = 16

Il y a cependant un problème : ce n'est pas cross-compatible. Oui, sous g++ un int est de 4 octets et un double est de 8 octets, mais ce n'est pas nécessairement toujours vrai (leur alignement ne doit pas être le même non plus), donc dans un environnement différent, cette "correction" pourrait non seulement être inutile, mais elle pourrait aussi potentiellement aggraver les choses en augmentant la quantité de remplissage nécessaire.

Existe-t-il un moyen fiable et multiplateforme de résoudre ce problème ? (minimiser la quantité de rembourrage nécessaire sans souffrir d'une baisse de performance due à un mauvais alignement ) ? Pourquoi le compilateur n'effectue-t-il pas ces optimisations ? (échanger les membres de la structure/classe pour réduire le rembourrage) ?

Clarification

En raison d'un malentendu et d'une confusion, je tiens à souligner que Je ne veux pas "emballer" mon struct . C'est-à-dire que je ne veux pas que ses membres soient non alignés et donc plus lents à accéder. Au lieu de cela, je veux toujours que tous les membres soient auto-alignés, mais d'une manière qui utilise le moins de mémoire possible sur le padding. Ce problème peut être résolu en utilisant, par exemple, un réarrangement manuel tel que décrit ici et dans le document L'art perdu de faire ses bagages par Eric Raymond. Je suis à la recherche d'un moyen automatisé et aussi multiplateforme que possible de faire cela, similaire à ce qui est décrit dans le document suivant proposition P1112 pour la future norme C++20.

2voto

supercat Points 25534

Bien que la norme accorde aux implémentations une grande discrétion pour insérer des quantités arbitraires d'espace entre les membres de la structure, c'est parce que les auteurs ne voulaient pas essayer de deviner toutes les situations où le remplissage pourrait être utile, et le principe "ne pas gaspiller d'espace sans raison" a été considéré comme évident.

En pratique, presque toutes les implémentations courantes pour du matériel courant utiliseront des objets primitifs dont la taille est une puissance de deux, et dont l'alignement requis est une puissance de deux qui n'est pas supérieure à la taille. De plus, presque toutes ces implémentations placeront chaque membre d'une structure au premier multiple disponible de son alignement qui suit complètement le membre précédent.

Certains pédants s'empresseront de dire que le code qui exploite ce comportement est "non portable". À eux, je répondrai

Le code C peut être non portable. Bien qu'il se soit efforcé de donner aux programmeurs la possibilité d'écrire des programmes réellement portables, le comité C89 n'a pas voulu forcer les programmeurs à écrire de manière portable, pour empêcher l'utilisation du C comme "assembleur de haut niveau" : la capacité d'écrire du code spécifique à la machine est l'une des forces du C.

Dans le prolongement de ce principe, la capacité d'un code qui ne doit s'exécuter que sur 90 % des machines à exploiter des caractéristiques communes à ces 90 % de machines - même si ce code n'est pas exactement "spécifique à la machine" - est l'une des forces du C. L'idée que les programmeurs C ne devraient pas avoir à se plier en quatre pour s'adapter aux limitations d'architectures qui, depuis des décennies, ne sont utilisées que dans les musées devrait être évidente, mais ne l'est apparemment pas.

1voto

Michael Points 161

Vous puede utiliser #pragma pack(1) mais la raison même de ceci est que le compilateur optimise. Accéder à une variable par le registre complet est plus rapide que d'y accéder par le moindre bit.

L'emballage spécifique n'est utile que pour la sérialisation et la compatibilité entre compilateurs, etc.

Comme NathanOliver l'a ajouté à juste titre, cela pourrait même échouer en cas de certaines plateformes .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X