Je souhaite stocker des types de données mixtes dans un tableau. Comment pourrait-on faire ça?
c
arrays
variant
mixed-type
Chanzerre
la source
la source
Réponses:
Vous pouvez faire des éléments du tableau une union discriminée, c'est-à-dire une union étiquetée .
Le
type
membre est utilisé pour contenir le choix du membre quiunion
doit être utilisé pour chaque élément du tableau. Donc, si vous voulez stocker unint
dans le premier élément, vous feriez:Lorsque vous souhaitez accéder à un élément du tableau, vous devez d'abord vérifier le type, puis utiliser le membre correspondant de l'union. Une
switch
déclaration est utile:Il appartient au programmeur de s'assurer que le
type
membre correspond toujours à la dernière valeur stockée dans le fichierunion
.la source
Utilisez un syndicat:
Vous devrez cependant garder une trace du type de chaque élément.
la source
Les éléments de tableau doivent avoir la même taille, c'est pourquoi ce n'est pas possible. Vous pouvez contourner ce problème en créant un type de variante :
La taille de l'élément de l'union est la taille du plus grand élément, 4.
la source
Il existe un style différent de définition du tag-union (quel que soit le nom) que l'OMI rend beaucoup plus agréable à utiliser , en supprimant l'union interne. C'est le style utilisé dans le système X Window pour des choses comme les événements.
L'exemple de la réponse de Barmar donne le nom
val
à l'union interne. L'exemple de la réponse de Sp. utilise une union anonyme pour éviter d'avoir à spécifier le.val.
chaque fois que vous accédez à l'enregistrement de variante. Malheureusement, les structures et unions internes "anonymes" ne sont pas disponibles dans C89 ou C99. C'est une extension de compilateur, et donc par nature non portable.Une meilleure façon de l'OMI est d'inverser toute la définition. Faites de chaque type de données sa propre structure et placez la balise (spécificateur de type) dans chaque structure.
Ensuite, vous les enveloppez dans une union au plus haut niveau.
Maintenant, il peut sembler que nous nous répétons, et nous le sommes . Mais considérez que cette définition est susceptible d'être isolée dans un seul fichier. Mais nous avons éliminé le bruit lié à la spécification de l'intermédiaire
.val.
avant d'accéder aux données.Au lieu de cela, il va à la fin, où c'est moins odieux. :RÉ
Une autre chose que cela permet est une forme d'héritage. Edit: cette partie n'est pas le C standard, mais utilise une extension GNU.
Montée et descente.
Edit: Une chose à savoir est si vous en construisez un avec des initialiseurs désignés C99. Tous les initialiseurs de membre doivent passer par le même membre du syndicat.
L'
.tag
initialiseur peut être ignoré par un compilateur d'optimisation, car l'.int_
initialiseur qui suit les alias utilise la même zone de données. Même si nous connaissons la mise en page (!), Et cela devrait être correct. Non, ce n'est pas le cas. Utilisez plutôt la balise "internal" (elle recouvre la balise externe, comme nous le voulons, mais ne confond pas le compilateur).la source
.int_.val
n'alias pas la même zone car le compilateur sait qu'il.val
s'agit d'un décalage supérieur à.tag
. Avez-vous un lien vers une discussion plus approfondie sur ce problème allégué?Vous pouvez créer un
void *
tableau, avec un tableau séparé desize_t.
Mais vous perdez le type d'information.Si vous avez besoin de conserver le type d'informations d'une manière ou d'une autre, conservez un troisième tableau de int (où int est une valeur énumérée), puis codez la fonction qui caste en fonction de la
enum
valeur.la source
L'union est la voie à suivre. Mais vous avez également d'autres solutions. L'un de ceux-ci est le pointeur étiqueté , qui consiste à stocker plus d'informations dans les bits "libres" d'un pointeur.
Selon les architectures, vous pouvez utiliser les bits faibles ou élevés, mais le moyen le plus sûr et le plus portable consiste à utiliser les bits faibles inutilisés en tirant parti de la mémoire alignée. Par exemple dans les systèmes 32 bits et 64 bits, les pointeurs vers
int
doivent être des multiples de 4 (en supposant qu'ilint
s'agit d'un type 32 bits) et les 2 bits les moins significatifs doivent être 0, vous pouvez donc les utiliser pour stocker le type de vos valeurs . Bien sûr, vous devez effacer les bits de balise avant de déréférencer le pointeur. Par exemple, si votre type de données est limité à 4 types différents, vous pouvez l'utiliser comme ci-dessousSi vous pouvez vous assurer que les données sont alignées sur 8 octets (comme pour les pointeurs dans les systèmes 64 bits, ou
long long
etuint64_t
...), vous aurez un bit de plus pour la balise.Cela présente un inconvénient: vous aurez besoin de plus de mémoire si les données n'ont pas été stockées dans une variable ailleurs. Par conséquent, si le type et la plage de vos données sont limités, vous pouvez stocker les valeurs directement dans le pointeur. Cette technique a été utilisée dans la version 32 bits du moteur V8 de Chrome , où il vérifie le bit le moins significatif de l'adresse pour voir s'il s'agit d'un pointeur vers un autre objet (comme un double, de gros entiers, une chaîne ou un objet) ou un 31 valeur signée -bit (appelée
smi
- petit entier ). Si c'est unint
, Chrome effectue simplement un décalage arithmétique à droite de 1 bit pour obtenir la valeur, sinon le pointeur est déréférencé.Sur la plupart des systèmes 64 bits actuels, l'espace d'adressage virtuel est encore beaucoup plus étroit que 64 bits, par conséquent les bits les plus significatifs peuvent également être utilisés comme balises . En fonction de l'architecture, vous avez différentes manières de les utiliser comme balises. ARM , 68k et bien d'autres peuvent être configurés pour ignorer les bits supérieurs , vous permettant de les utiliser librement sans vous soucier de segfault ou quoi que ce soit. De l'article Wikipédia lié ci-dessus:
Sur x86_64, vous pouvez toujours utiliser les bits hauts comme balises avec précaution . Bien sûr, vous n'avez pas besoin d'utiliser tous ces 16 bits et pouvez omettre certains bits pour une preuve future
Dans les versions précédentes de Mozilla Firefox, ils utilisent également de petites optimisations d'entiers comme la V8, avec les 3 bits faibles utilisés pour stocker le type (int, chaîne, objet ... etc.). Mais depuis JägerMonkey, ils ont emprunté un autre chemin ( la nouvelle représentation de la valeur JavaScript de Mozilla , lien de sauvegarde ). La valeur est désormais toujours stockée dans une variable à double précision 64 bits. Lorsque le
double
est normalisé , il peut être utilisé directement dans les calculs. Cependant, si les 16 bits supérieurs de celui-ci sont tous des 1, qui désignent un NaN , les 32 bits inférieurs stockeront l'adresse (dans un ordinateur 32 bits) à la valeur ou la valeur directement, les 16 bits restants seront utilisés pour stocker le type. Cette technique s'appelle NaN-boxingou nun-boxe. Il est également utilisé dans JavaScriptCore 64 bits de WebKit et SpiderMonkey de Mozilla avec le pointeur stocké dans les 48 bits bas. Si votre type de données principal est à virgule flottante, c'est la meilleure solution et offre de très bonnes performances.En savoir plus sur les techniques ci-dessus: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations
la source