89 votes

Pourquoi les compilateurs C ne peuvent-ils pas réorganiser les membres de la structure pour éliminer le remplissage d'alignement?

Double Possible:
Pourquoi ne pas GCC optimiser les structures?
Pourquoi ne pas en C++ rendre la structure plus serré?

Considérons l'exemple suivant sur un 32 bits x86 machine:

En raison de l'harmonisation des contraintes, la structure suivante

struct s1 {
    char a;
    int b;
    char c;
    char d;
    char e;
}

pourrait être représenté plus de la mémoire efficacement (12 contre 8 octets) si les membres ont été réorganisées comme dans

struct s2 {
    int b;
    char a;
    char c;
    char d;
    char e;
}

Je sais que les compilateurs C/C++ ne sont pas autorisés à le faire. Ma question est pourquoi le langage a été conçu de cette façon. Après tout, nous risquons de perdre de grandes quantités de mémoire, et des références telles que struct_ref->b ne serait pas de soins à propos de la différence.

EDIT: Merci à tous pour votre très utile réponses. Vous expliquez très bien pourquoi réorganiser ne fonctionne pas en raison de la façon dont le langage a été conçu. Toutefois, il me fait penser: est-ce que ces arguments serais encore si le réarrangement a été une partie de la langue? Disons qu'il y a un certain réaménagement de la règle, à partir de laquelle nous fallait au moins que

  1. il faut réorganiser la structure si nécessaire (ne pas faire quelque chose si la structure est déjà "serré")
  2. la règle ne regarde que la définition de la structure, pas à l'intérieur intérieur des structures. Cela garantit qu'un type struct a la même disposition si oui ou non il est interne dans une autre structure
  3. l'compilé disposition de la mémoire d'un struct est prévisible compte tenu de sa définition (qui est, la règle est fixe)

L'adressage de vos arguments un par un, j'ai raison:

  • Faible niveau de cartographie des données, "élément de moindre surprise": il suffit d'écrire votre structs en respectant le style de vous-même (comme dans @Perry réponse) et rien n'a changé (condition 1). Si, pour une raison ou une autre, vous souhaitez rembourrage interne pour être là, vous pouvez insérer manuellement à l'aide de variables muettes, et/ou il pourrait y avoir des mots-clés/directives.

  • Compilateur différences: Exigence 3 élimine ce problème. En fait, à partir de @David Heffernan les commentaires, il semble que nous avons ce problème aujourd'hui, parce que les différents compilateurs pad différemment?

  • Optimisation: Le point de l'ensemble de réorganisation est (de mémoire) d'optimisation. Je vois beaucoup de potentiel ici. Nous pourrions ne pas être en mesure de retirer le rembourrage tous ensemble, mais je ne vois pas comment la réorganisation pourrait limiter l'optimisation d'une quelconque façon.

  • Type de casting: Il me semble que c'est le plus gros problème. Encore, il devrait y avoir des façons de contourner cela. Puisque les règles sont fixées dans le langage, le compilateur est capable de comprendre la manière dont les membres ont été réorganisées, et de réagir en conséquence. Comme mentionné ci-dessus, il sera toujours possible de prévenir la réorganisation dans le cas où vous voulez un contrôle total. Aussi, l'exigence 2 s'assure que le code de type sécurisé ne peut se briser.

La raison pour laquelle je pense que cette règle peut faire sens est parce que je trouve plus naturel de groupe les membres de la structure, de par leur contenu que par leurs types. Aussi il est plus facile pour le compilateur de choisir le meilleur de la commande que c'est pour moi, quand j'ai beaucoup de l'intérieur des structures. La disposition optimale peut être encore une que je ne peux pas exprimer dans un type de façon sécuritaire. D'autre part, il semblerait indiquer que la langue la plus compliquée, ce qui est un inconvénient.

Notez que je ne parle pas de changer la langue, et seulement si, il pourrait(/devrait) ont été conçus différemment.

Je sais que ma question est hypothétique, mais je pense que la discussion fournit une compréhension plus profonde dans les niveaux inférieurs de la machine et la langue de conception.

Je suis assez nouveau ici, donc je ne sais pas si je dois pondre une nouvelle question. S'il vous plaît dites-moi si c'est le cas.

73voto

Atom Points 8739

Il y a plusieurs raisons pourquoi le compilateur C ne peut pas automatiquement réorganiser les champs:

  • Le compilateur C ne sait pas si l' struct représente la mémoire de la structure des objets au-delà de l'unité de compilation (par exemple: un étranger bibliothèque, un fichier sur le disque, les données du réseau, CPU tables de page, ...). Dans un tel cas, la structure binaire de données est également définie dans un endroit inaccessible pour le compilateur, de sorte que la réorganisation de l' struct les champs de créer un type de données qui est incompatible avec les autres définitions. Par exemple, l' en-tête d'un fichier dans un fichier ZIP qui contient plusieurs alignées champs de 32 bits. Réordonner les champs qu'il serait impossible pour le code C pour lire ou écrire directement l'en-tête (en supposant que le ZIP de la mise en œuvre souhaitez accéder aux données directement):

    struct __attribute__((__packed__)) LocalFileHeader {
        uint32_t signature;
        uint16_t minVersion, flag, method, modTime, modDate;
        uint32_t crc32, compressedSize, uncompressedSize;
        uint16_t nameLength, extraLength;
    };
    

    L' packed attribut empêche le compilateur, de l'alignement des champs selon leur alignement naturel, et il n'a aucun rapport avec le problème de l'ordre des champs. Il serait possible de les réorganiser les domaines de l' LocalFileHeader , de sorte que la structure est à la fois la taille minimale et a tous les champs alignés à leur alignement naturel. Cependant, le compilateur ne peut pas choisir de réorganiser les champs car il ne sait pas que la structure est en fait défini par la spécification de fichier ZIP.

  • C est une mauvaise langue. Le compilateur C ne sait pas si les données seront accessibles par l'intermédiaire d'un type différent de celui vu par le compilateur, par exemple:

    struct S {
        char a;
        int b;
        char c;
    };
    
    struct S_head {
        char a;
    };
    
    struct S_ext {
        char a;
        int b;
        char c;
        int d;
        char e;
    };
    
    struct S s;
    struct S_head *head = (struct S_head*)&s;
    fn1(head);
    
    struct S_ext ext;
    struct S *sp = (struct S*)&ext;
    fn2(sp);
    

    C'est un largement utilisé la programmation de bas niveau modèle, surtout si l'en-tête contient l'ID du type de données situé juste au-delà de l'en-tête.

  • Si un struct type est incorporé dans un autre struct type, il est impossible de l'inclure à l'intérieur struct:

    struct S {
        char a;
        int b;
        char c, d, e;
    };
    
    struct T {
        char a;
        struct S s; // Cannot inline S into T, 's' has to be compact in memory
        char b;
    };
    

    Cela signifie également que le déplacement de certains champs de S vers une struct désactive certaines optimisations:

    // Cannot fully optimize S
    struct BC { int b; char c; };
    struct S {
        char a;
        struct BC bc;
        char d, e;
    };
    
  • Parce que la plupart des compilateurs C sont l'optimisation des compilateurs, la réorganisation des struct champs nécessiterait de nouvelles optimisations pour être mis en œuvre. Il est douteux que ces optimisations seraient en mesure de faire mieux que ce que les programmeurs sont capables d'écrire. La conception de structures de données à la main est beaucoup moins de temps que d'autres compilateur des tâches telles que l'allocation de registres, la fonction inline, de constantes, de la transformation d'une instruction switch en binaire de recherche, etc. Ainsi, les avantages en permettant au compilateur d'optimiser les structures de données semblent être moins tangibles que les traditionnels optimisations du compilateur.

31voto

Perry Points 2241

C est conçu et destiné à rendre possible l'écriture non-matériel portable et le format dépendant de code dans un langage de haut niveau. Le réarrangement de la structure de contenu derrière le dos de la programmeur aurait détruire cette capacité.

Observer le présent code de NetBSD ip.h:


/*
 * Structure of an internet header, naked of options.
 */
struct ip {
#if BYTE_ORDER == LITTLE_ENDIAN
    unsigned int ip_hl:4,       /* header length */
             ip_v:4;        /* version */
#endif
#if BYTE_ORDER == BIG_ENDIAN
    unsigned int ip_v:4,        /* version */
             ip_hl:4;       /* header length */
#endif
    u_int8_t  ip_tos;       /* type of service */
    u_int16_t ip_len;       /* total length */
    u_int16_t ip_id;        /* identification */
    u_int16_t ip_off;       /* fragment offset field */
    u_int8_t  ip_ttl;       /* time to live */
    u_int8_t  ip_p;         /* protocol */
    u_int16_t ip_sum;       /* checksum */
    struct    in_addr ip_src, ip_dst; /* source and dest address */
} __packed;

Cette structure est identique dans la mise en page à l'en-tête d'un datagramme IP. Il est utilisé pour interpréter directement les gouttes de mémoire blatted par un contrôleur ethernet IP datagramme en-têtes. Imaginez si le compilateur arbitrairement ré-arrangé le contenu de la vertu de l'auteur, ce serait une catastrophe.

Et oui, il n'est pas précisément portable (et il y a même un non-portable gcc directive donnée par le biais de l' __packed macro) mais ce n'est pas le point. C est spécifiquement conçu pour rendre possible l'écriture non-portable haut niveau de code pour la conduite du matériel. C'est sa fonction dans la vie.

11voto

perreal Points 47912

C [et C ++] sont considérés comme des langages de programmation système et fournissent donc un accès de bas niveau au matériel, par exemple, la mémoire au moyen de pointeurs. Le programmeur peut accéder à un bloc de données, le convertir en structure et accéder à divers membres [facilement].

Un autre exemple est une structure semblable à celle ci-dessous, qui stocke des données de taille variable.

 struct {
  uint32_t data_size;
  uint8_t  data[1]; // this has to be the last member
} _vv_a;
 

10voto

John Bode Points 33046

N'étant pas un membre de WG14, je ne peux pas dire quelque chose de définitif, mais j'ai mes propres idées:

  1. Il serait violer le principe de moindre surprise: il y a peut être une maudite bonne raison pourquoi je veux poser ma éléments dans un ordre précis, indépendamment de si oui ou non il est le plus efficace en terme d'espace, et je ne voudrais pas que le compilateur pour réorganiser ces éléments;

  2. Il a le potentiel de briser un montant non négligeable de code existant, il y a beaucoup de code legacy qui s'appuie sur des choses comme l'adresse de la structure étant la même que l'adresse du premier membre (vu beaucoup de classique de MacOS code qui fait que l'hypothèse);

Le C99 Justification s'adresse directement au deuxième point ("code Existant est important, implémentations existantes ne sont pas") et, indirectement, traite de la première de Fiducie ("le programmeur").

9voto

vicatcu Points 2583

Cela changerait la sémantique des opérations de pointeur pour réorganiser les membres de la structure. Si vous êtes intéressé par la représentation de la mémoire compacte, il est de votre responsabilité de connaître votre architecture cible et d'organiser vos structures en conséquence.

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