Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

alignements struct/tableaux

19 réponses
Avatar
Pierre Habouzit
soit le bout de code suivant :

------------------------------------------------------------------------

typedef struct foo foo;

struct my_union {
union {
foo *array[2];
struct {
foo *first;
foo *second;
}
}
} my_union;

------------------------------------------------------------------------

soit 'bar' du type my_union, en supposant que tous les membres sont alloués.

est ce que je suis toujours sur que :

&bar->array[0] == &bar->first
&bar->array[1] == &bar->second

autrement dit, si j'ai un tableau de N pointeurs, et une structure avec N
membres du même type, je suis toujours assuré que les alignements sont les
mêmes, et que je peux du coup utiliser indépendemment l'une ou l'autre des
écritures ?


PS: je me doute que je peux faire un #define MY_UNION_FIRST 0 et
#define MY_UNION_SECOND 1 et utiliser des

bar->array[MY_UNION_FIRST/SECOND]

mais la concision d'une telle écriture laisse fortement à désirer, et
j'ai besoin d'accéder à mes pointeurs via des offsets.
--
·O· Pierre Habouzit
··O madcoder@debian.org
OOO http://www.madism.org

10 réponses

1 2
Avatar
Harpo
Pierre Habouzit wrote:

est ce que je suis toujours sur que :

&bar->array[0] == &bar->first


J'ai déjà posé cette question ici, de manière un peu moins compliquée,
et peut-être que ce n'était pas exactement la même chose, mais la
réponse était 'oui'.
A moins que vous ne m'ayez trompé par vos absences de parenthèses.

Avatar
Emmanuel Delahaye
Pierre Habouzit wrote on 17/09/05 :
typedef struct foo foo;

struct my_union {
union {
foo *array[2];
struct {
foo *first;
foo *second;
}
}
} my_union;

soit 'bar' du type my_union, en supposant que tous les membres sont alloués.

est ce que je suis toujours sur que :

&bar->array[0] == &bar->first


Oui, mais par contre

&bar->array[1] == &bar->second


ceci n'est pas garanti par la norme.

D'une façon générale, ce n'est pas une bonne idée d'écrire dans un
membre d'une union est de lire dans un autre. Une union, c'est pas fait
pour ça.

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

.sig under repair

Avatar
Richard Delorme
soit le bout de code suivant :

------------------------------------------------------------------------

typedef struct foo foo;

struct my_union {
union {
foo *array[2];
struct {
foo *first;
foo *second;
}
}
} my_union;

------------------------------------------------------------------------

soit 'bar' du type my_union, en supposant que tous les membres sont alloués.

est ce que je suis toujours sur que :

&bar->array[0] == &bar->first


oui.
On est aussi toujours sûr (sauf bug du compilo) que les membres d'une
union partagent la même adresse. On sait aussi que le premier élément
d'un tableau ou d'une structure on la même adresse que le tableau ou la
structure.

&bar->array[1] == &bar->second


non.

Dans une structure, les membres ne sont pas nécessairement contigus. Il
peut y avoir des remplissages non nommées ("unnamed padding") entre les
membres et à la fin de la structure, mais pas au début. Par contre, les
éléments d'un tableau sont contigus, donc la seconde égalité n'est pas
nécessairement vérifiée.

--
Richard

Avatar
Pierre Habouzit
&bar->array[0] == &bar->first


Oui, mais par contre

&bar->array[1] == &bar->second


ceci n'est pas garanti par la norme.

D'une façon générale, ce n'est pas une bonne idée d'écrire dans un
membre d'une union est de lire dans un autre. Une union, c'est pas fait
pour ça.


hmmm, c'est bien dommage.

J'ai des structures dont un des membres est l'indice dans le tableau de mon
union, du pointeur auquel il font référence. ca m'aurait permis de faire
des trucs du genre :

bar->array[baz->some_index]

Mais lorsque j'y accède textuellement je voulais éviter des
bar->array[MY_UNION_FIRST] ... et donc pouvoir accéder aux pointeurs du
tableau par leur "nom".


le fait de ne pas pouvoir assurer une telle propriété est tout de même bien
dommage. enfin, tant pis, en virant l'union j'aurai le même comportement de
toute facon ... au cout de 16 octets de perdus ;p
--
·O· Pierre Habouzit
··O
OOO http://www.madism.org


Avatar
harpo
On Sat, 17 Sep 2005 21:04:24 +0200, Pierre Habouzit wrote:


hmmm, c'est bien dommage.

J'ai des structures dont un des membres est l'indice dans le tableau de mon
union, du pointeur auquel il font référence. ca m'aurait permis de faire
des trucs du genre :

bar->array[baz->some_index]

Mais lorsque j'y accède textuellement je voulais éviter des
bar->array[MY_UNION_FIRST] ... et donc pouvoir accéder aux pointeurs du
tableau par leur "nom".


Excusez-moi, mais j'ai du mal à comprendre votre problème; ce n'est pas
à la portée d'un débutant, est-ce que vous pourriez le préciser pour
que tout le monde suive ?

Avatar
Pierre Habouzit
harpo wrote:

On Sat, 17 Sep 2005 21:04:24 +0200, Pierre Habouzit wrote:


hmmm, c'est bien dommage.

J'ai des structures dont un des membres est l'indice dans le tableau de
mon union, du pointeur auquel il font référence. ca m'aurait permis de
faire des trucs du genre :

bar->array[baz->some_index]

Mais lorsque j'y accède textuellement je voulais éviter des
bar->array[MY_UNION_FIRST] ... et donc pouvoir accéder aux pointeurs du
tableau par leur "nom".


Excusez-moi, mais j'ai du mal à comprendre votre problème; ce n'est pas
à la portée d'un débutant, est-ce que vous pourriez le préciser pour
que tout le monde suive ?


pour mettre un peu de background, j'ai (ou plutot j'aurais aimé avoir) une
première structure :

struct message {
[ ... pleins de choses dont pas mal de 'headers' ...];

union {
struct {
const blob_t *original;
blob_t *scratch;
};
blob_t *__bufs[2];
};

[ ... encore pleins de choses ...];
};


struct header {
struct { /* expansé, en fait c'est un sous type */
ssize_t start;
ssize_t size;
} value;

[ ... qqs trucs ...];

int modified:1;
[ ... d'autres bits ...];
};


un blob_t est une structure qui me permet de gérer des tableaux d'octets
quelconques (pour faire simple). et un header définit une sous partie d'un
des deux buffer (original ou scratch) de ma structure message.

j'ai potentiellement pas mal de header, et je ne veux donc pas y avoir de
pointeur sur un blob_t*, d'autant que j'ai déjà d'autres bits dans la
structure.

avec une telle construction, ca m'aurait permis d'écrire :

msg->__bufs[hdr->modified]

pour accéder au bon blob_t qui contient la valeur du header. je voulais
éviter d'avoir à écrire :

(hdr->modified) ? msg->scratch : msg->original;

si je n'avais gardé que la struct dans l'union, ou bien pire, définir des
MESSAGE_ORIGNINAL à 0 et MESSAGE_SCRATCH à 1 pour pouvoir accéder aux deux
buffers dans mes fonctions où je les manipule, via des
msg->bufs[MESSAGE_ORIGINAL].


mais du coup, vu que je ne peux garantir que scratch coincide avec la
deuxième case du tablea __bufs, je me suis résigné à avoir comme type pour
mes messages :

struct message {
[ ... pleins de choses dont pas mal de 'headers' ...];

struct {
const blob_t *original;
blob_t *scratch;
};
blob_t *__bufs[2];

[ ... encore pleins de choses ...];
};

ce qui me force à maintenir l'invariant __bufs[0] == original et
__bufs[1]==scratch à la main, mais vu que ces pointeurs changent peu, c'est
pas trop génant pour moi, et autant je ne voulais pas perdre la taille d'un
pointeur en plus dans mes headers vu que j'en ai beaucoup et que j'avais
déjà des champs de bits, mais perdre 16 octets dans la structure de message
n'est pas trop génant. (enfin 16 ou plutot sizof(blob_t*)*2)


--
·O· Pierre Habouzit
··O
OOO http://www.madism.org


Avatar
Pierre Habouzit
Emmanuel Delahaye wrote:

D'une façon générale, ce n'est pas une bonne idée d'écrire dans un
membre d'une union est de lire dans un autre. Une union, c'est pas fait
pour ça.


soit dit en passant du coup, je me demande vraiment à quoi ca sert d'autre
que de permettre des vues différentes de la même structure sous-jacente.

Parce que si il s'agit juste de pouvoir "embarquer" plusieurs types
différents, autant utiliser une zone mémoire pointée depuis un void* ... ca
offre à peu près les même propriétés.


Sinon *juste pour ma culture personnelle* -- je ne compte certainement pas
l'utiliser si la norme l'interdit ou ne le spécifie pas -- y a-t-il
vraiment *beaucoup* de compilateurs/architectures où une struct avec deux
pointeurs sur le même type, ne sont pas alignés pareil que un tableau avec
deux de ces pointeurs ?

(sans doute les archis où les pointeurs ont moins de bits que l'entier le
plus grand manipulable par le processeur. mais je doute que de telles
archis soient légion ...)

donc reposons la question autrement. si my_type est un type qui représente
la plus grosse valeur entière manipulable par le système (c'est pas
rigoureux comme définition, mais vous comprendrez sans doute mon point).
est-ce stupide de supposer que :

struct {
my_type a;
my_type b;
}

et my_type c[2] vont être alignés pareils ?

--
·O· Pierre Habouzit
··O
OOO http://www.madism.org

Avatar
Emmanuel Delahaye
Pierre Habouzit wrote on 18/09/05 :
Parce que si il s'agit juste de pouvoir "embarquer" plusieurs types
différents, autant utiliser une zone mémoire pointée depuis un void* ... ca
offre à peu près les même propriétés.
Le void* n'est pas déterministe. Il est totalement souple par nature et

convient à la généricité totale avec le corollaire qui va avec : aucun
contrôle de type.

Les unions (avec sélecteur) servent à la généricité définie et évitent
les abominations que sont les fonctions 'variadics'.

Sinon *juste pour ma culture personnelle* -- je ne compte certainement pas
l'utiliser si la norme l'interdit ou ne le spécifie pas -- y a-t-il
vraiment *beaucoup* de compilateurs/architectures où une struct avec deux
pointeurs sur le même type, ne sont pas alignés pareil que un tableau avec
deux de ces pointeurs ?


Il suffit d'un, passé, présent ou à venir.

donc reposons la question autrement. si my_type est un type qui représente
la plus grosse valeur entière manipulable par le système (c'est pas
rigoureux comme définition, mais vous comprendrez sans doute mon point).
est-ce stupide de supposer que :


On peut supposer ce qu'on veut, mais si ce n'est pas défini par la
norme, faut pas se plaindre le jour où ça pète...

Depuis que je travaille dans l'informatique (15 ans), j'ai utilisé une
dizaine d'architectures et une quinzaine de compilateurs différents.
Des trucs bizarres (y compris hors norme), j'en ai vu pas mal... En
embarqué c'est un peu le bazar parfois...

Quand on ne fait que du Wintel, on a l'impression (fausse) que les
choses sont, disons, établies.

La réalité est différentes, et les gens astucieux qui ont spécifiés le
langage C en ont tenu compte, puisque malheureusement, il a fallu faire
avec l'existant et des contraintes parfois contradictoires...

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

.sig under repair

Avatar
Harpo
Pierre Habouzit wrote:

Merci pour l'explication, je crois avoir compris jusqu'ici.

mais du coup, vu que je ne peux garantir que scratch coincide avec la
deuxième case du tablea __bufs, je me suis résigné à avoir comme type
pour mes messages :

struct message {
[ ... pleins de choses dont pas mal de 'headers' ...];


Je ne comprends plus à partir d'ici.

struct {
const blob_t *original;
blob_t *scratch;
};


Pourquoi garder la structure au dessus, et -peut-être suis-je mal
réveillé- comment y accéder sans la nommer ?

blob_t *__bufs[2];

[ ... encore pleins de choses ...];
};

ce qui me force à maintenir l'invariant __bufs[0] == original et
__bufs[1]==scratch à la main, mais vu que ces pointeurs changent peu,
c'est pas trop génant pour moi, et autant je ne voulais pas perdre la
taille d'un pointeur en plus dans mes headers vu que j'en ai beaucoup
et que j'avais déjà des champs de bits, mais perdre 16 octets dans la
structure de message n'est pas trop génant. (enfin 16 ou plutot
sizof(blob_t*)*2)


Ou 8 suivant la taille du pointeur.
Mais je n'en vois pas l'interêt.

Pourquoi cela au-dessous ne suffirait-il pas ? :

struct message {
[ ... pleins de choses dont pas mal de 'headers' ...];
blob_t *__bufs[2];
[ ... encore pleins de choses ...];
};

d'autant que la valeur du bit modified est l'indice du blob_t * et que
son nom est judicieusement choisi, étant à 0 lorsque le blob_t n'est
pas modifié.

De plus, je ne vois pas l'intérêt d'éloigner le bit modified du tableau
de pointeurs en le mettant dans un (ou des) header, puisque le fait que
le buffer soit modifié semble dépendre du message et non de ces
headers.
Mais je n'ai peut-être pas tout compris.

Je me sers assez peu des unions, seulement lorsque des choses qui n'ont
a priori pas grand chose à voir entre elles peuvent occuper un même
espace du fait qu'elles ne peuvent être présentes en même temps.

Avatar
Pierre Habouzit
Harpo wrote:

Pierre Habouzit wrote:

Merci pour l'explication, je crois avoir compris jusqu'ici.

mais du coup, vu que je ne peux garantir que scratch coincide avec la
deuxième case du tablea __bufs, je me suis résigné à avoir comme type
pour mes messages :

struct message {
[ ... pleins de choses dont pas mal de 'headers' ...];


Je ne comprends plus à partir d'ici.

struct {
const blob_t *original;
blob_t *scratch;
};


Pourquoi garder la structure au dessus, et -peut-être suis-je mal
réveillé- comment y accéder sans la nommer ?


bah tu peux y accéder par hdr->scratch.

blob_t *__bufs[2];

[ ... encore pleins de choses ...];
};

ce qui me force à maintenir l'invariant __bufs[0] == original et
__bufs[1]==scratch à la main, mais vu que ces pointeurs changent peu,
c'est pas trop génant pour moi, et autant je ne voulais pas perdre la
taille d'un pointeur en plus dans mes headers vu que j'en ai beaucoup
et que j'avais déjà des champs de bits, mais perdre 16 octets dans la
structure de message n'est pas trop génant. (enfin 16 ou plutot
sizof(blob_t*)*2)


Ou 8 suivant la taille du pointeur.
Mais je n'en vois pas l'interêt.

Pourquoi cela au-dessous ne suffirait-il pas ? :

struct message {
[ ... pleins de choses dont pas mal de 'headers' ...];
blob_t *__bufs[2];
[ ... encore pleins de choses ...];
};


parce que lorsque je code et que j'utilise ma structure de message, je ne
veux pas passer par __bufs et écrire des msg->__bufs[0] ou msg->__bufs[1]
et me souvenir lorsque je code que __bufs[0] est mon buffer originel, et
bufs[1] celui où je fait mes modifs.

Dans mon module C je veux utiliser msg->original et msg->scratch qui eux
sont clairs, et en plus comme original a un const en plus, il m'évite la
bêtise d'aller le modifier si je ne suis plus très réveillé.


De plus, je ne vois pas l'intérêt d'éloigner le bit modified du tableau
de pointeurs en le mettant dans un (ou des) header, puisque le fait que
le buffer soit modifié semble dépendre du message et non de ces
headers.
Mais je n'ai peut-être pas tout compris.
sur ce point en effet. mon message est un message ressemblant à un mail (en

fait c'est pas trop ca, mais ca se comporte pareil). Je fais une lib pour
manipuler ces messages, et des fois je vais changer *quelques* headers. Or
je ne veux pas dupliquer toutes les infos, et si je change un header, je
vais positionner son bit "modified", allouer la valeur du header dans le
blob scratch, et le segment auquel il fera référence sera non plus dans le
buffer originel, mais dans le buffer scratch.

mais pour autant, je ne modifie pas les autres headers qui eux référencent
toujours le buffer originel.
--
·O· Pierre Habouzit
··O
OOO http://www.madism.org


1 2