62 votes

Étendre une structure en C

Je suis récemment tombé sur le code d'un collègue qui ressemble à ceci:

 typedef struct A {
  int x;
}A;

typedef struct B {
  A a;
  int d;
}B;

void fn(){
  B *b;
  ((A*)b)->x = 10;
}
 

Son explication était que puisque struct A était le premier membre de struct B , donc b->x serait identique à b->a.x et offrirait une meilleure lisibilité.
Cela a du sens, mais est-ce considéré comme une bonne pratique? Et cela fonctionnera-t-il sur plusieurs plates-formes? Actuellement, cela fonctionne bien sur GCC.

67voto

paxdiablo Points 341644

Oui, il fonctionnera de la croix-plate-forme, mais ce n'est pas nécessairement une bonne idée.

Conformément à la norme ISO C standard (toutes les citations ci-dessous proviennent de C11), 6.7.2.1 Structure and union specifiers /15, il n'est pas permis à un rembourrage avant le premier élément d'une structure

En outre, 6.2.7 Compatible type and composite type membres qui:

Deux types sont compatibles type si leurs types sont les mêmes

et il est incontestable que l' A et A-within-B types sont identiques.

Cela signifie que les accès à la mémoire de l' A champs sera le même dans les deux A et B types, comme ce serait le plus judicieux b->a.x , ce qui est probablement ce que vous devriez utiliser si vous avez des préoccupations au sujet de la maintenabilité dans le futur.

Et, bien que vous auriez normalement à vous soucier de type strict de l'aliasing, je ne crois pas que cela s'applique ici. Il est illégal de faire un alias de pointeurs, mais la norme a des exceptions.

6.5 Expressions /7 membres de certaines de ces exceptions, avec la note de bas de page:

Le but de cette liste est de préciser les circonstances dans lesquelles un objet peut ou peut ne pas être un alias.

Les exceptions énumérées sont:

  • a type compatible with the effective type of the object;
  • certaines autres exceptions qui ne doivent pas nous intéresse ici; et
  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union).

Ce qui, combiné à la structure de rembourrage règles mentionnées ci-dessus, y compris la phrase:

Un pointeur vers une structure de l'objet, convenablement converti, points à son premier membre

semble indiquer que cet exemple est spécifiquement autorisées. Le point central, nous avons à retenir ici est que le type de l'expression ((A*)b) est A*, pas B*. Qui rend les variables compatible pour l'application de l'illimité d'alias.

C'est ma lecture des parties pertinentes de la norme, j'ai eu tort avant (a), mais j'en doute dans ce cas.

Donc, si vous avez un véritable besoin pour ce faire, il travaillera d'accord mais je serais documenter les contraintes dans le code très proche des structures de manière à ne pas vous faire piquer à l'avenir.


(a) Que ma femme va vous le dire, souvent et sans trop d'invites :-)

35voto

unwind Points 181987

Je vais aller sur une branche et s'opposer @paxdiablo sur celui-ci: je pense que c'est une belle idée, et il est très commun dans les grands, un code de qualité.

C'est essentiellement la plus évidente et la plus belle façon de mettre en œuvre l'héritage base orientée objet, structures de données en C. de Départ de la déclaration d' struct B avec une instance de struct A signifie "B est une sous-classe de". Le fait que le premier membre de structure est garantie 0 octets à partir du début de la structure est ce qui permet de travailler en toute sécurité, et c'est à la limite de beau à mon avis.

Il est largement utilisé et déployé dans le code en fonction de l' Objet de la bibliothèque, tels que le GTK+ outil d'interface utilisateur et de l'environnement de bureau GNOME.

Bien sûr, cela suppose que vous "vous savez ce que vous faites", mais qui est généralement toujours le cas lors de la mise en œuvre complexe de relations de type C. :)

Dans le cas de GObject et GTK+, il y a beaucoup de soutien de l'infrastructure et de la documentation pour aider à cela: il est très difficile de l'oublier. Cela pourrait signifier que la création d'une nouvelle classe n'est pas quelque chose que vous faites aussi rapidement qu'en C++, mais c'est peut-être à prévoir car il n'y a pas de support natif en C pour les classes.

12voto

jaket Points 2428

C'est une idée horrible. Dès que quelqu'un arrive et insère un autre champ au début de la structure B, votre programme explose. Et qu'est-ce qui ne va pas avec b.a.x ?

12voto

dureuill Points 1598

Tout ce qui contourne le type de la vérification doit généralement être évitée. Ce hack reposent sur l'ordre des déclarations et ni le casting, ni cette ordonnance peut être exécutée par le compilateur.

Il devrait fonctionner de la croix-plate-forme, mais je ne pense pas que c'est une bonne pratique.

Si vous avez vraiment des structures profondément imbriquées (vous pourriez avoir à se demander pourquoi, d'ailleurs), alors vous devriez utiliser un temporaire variable locale pour accéder aux champs:

A deep_a = e->d.c.b.a;
deep_a.x = 10;
deep_a.y = deep_a.x + 72;
e->d.c.b.a = deep_a;

Ou, si vous ne souhaitez pas copier a le long de:

A* deep_a = &(e->d.c.b.a);
deep_a->x = 10;
deep_a->y = deep_a->x + 72;

Cette montre d'où l' a vient et il n'a pas besoin d'un plâtre.

Java et C# aussi régulièrement exposer des constructions comme les "c.b.une", je ne vois pas quel est le problème. Si ce que vous voulez simuler est orienté objet de comportement, alors vous devez envisager d'utiliser un langage orienté objet (comme C++), depuis "l'extension des structures", dans le sens que vous proposez ne fournit pas d'encapsulation ni polymorphisme d'exécution (même si on peut arguer que ((A*)b), s'apparente à un "dynamic cast").

7voto

Vality Points 2294

Je suis désolé d'être en désaccord avec toutes les autres réponses ici, mais ce système n'est pas conforme à la norme C. Il n'est pas acceptable d'avoir deux pointeurs avec différents types de point au même endroit à la même heure, ce qui est appelé aliasing et n'est pas autorisé par la stricte aliasing règles en C99 et de nombreuses autres normes. Moins moche était de le faire serait d'utiliser en ligne de lecture fonctions qui alors ne pas avoir à chercher soigné de cette façon. Ou peut être est-ce le travail d'un syndicat? Spécifiquement autorisées à détenir un ou plusieurs types, cependant, il existe une myriade d'autres inconvénients.

En bref, ce genre de sale casting pour créer le polymorphisme n'est pas autorisé par la plupart des normes C, juste parce qu'elle semble fonctionner sur votre compilateur ne signifie pas qu'il est acceptable. Voir ici pour une explication de pourquoi il n'est pas permis, et pourquoi les compilateurs à haute niveaux d'optimisation peut casser le code qui ne respecte pas ces règles http://en.wikipedia.org/wiki/Aliasing_%28computing%29#Conflicts_with_optimization

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