OVH Cloud OVH Cloud

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

9 réponses

1 2
Avatar
harpo
On Sun, 18 Sep 2005 11:40:18 +0200, Pierre Habouzit wrote:


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.


#define ORIGINAL 0
#define MODIFIED 1
( ou l'équivalent avec enum )

Quand on veut explicitement accéder à un des buffers.
soit :
msg->__bufs[ ORIGINAL ]
msg->__bufs[ MODIFIED ]

Quand on veut y accéder sans savoir s'il a été ou non modifié, se
servir du bit modified,
Je ne vois rien d'obfuscant là dedans.

Je ne pense pas que rajouter des données inutiles dans des structures
puisse simplifier la lecture d'un programme, personnellement c'est
plutôt un truc qui me laisserait perplexe et soupçonner une siouxerie
pas nette en le lisant.

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é.


Le const en plus peut, par contre être un bon argument, mais je ne pense
pas qu'il soit déterminant.

Dans ce cas mets juste 2 pointers et des macros ou fonctions inline
get_buf_original( msg )
get_buf_modified( msg )

et une autre macro qui se sert du bit modified.

Je ne vois vraiment pas la raison pour s'embêter avec des struct et des
unions, c'est obfuscatoire.


Avatar
Pierre Maurette
[...]
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.
Il semble que l'idée derrière l'union soit celle du type variant d'autres

langages. Il faut encapsuler l'union dans une structure qui contient en plus
le renseignement de type.
J'ai fait un bout d'essai pour voir:

<code>
#include <stdio.h>
#include <stdlib.h>

enum {INIT_ERR, VARPRINT_ERR};
const char* service[] = {"Erreur initialisation, type inconnu"
, "Erreur varprint(), type inattendu"}
;

enum Type {ERROR_TYPE, REEL, ENTIER, CHAINE} ;


typedef struct {
enum Type t;
union {
double f;
int i;
const char* s;
};
} variant;


variant initvariant(enum Type t, const char* data)
{
variant ret = {t};
switch(t)
{
case REEL:
ret.f = atof(data);
break;
case ENTIER:
ret.i = atoi(data);
break;
case CHAINE:
ret.s = data;
break;
default:
ret.t = ERROR_TYPE;
ret.s = service[INIT_ERR];
}
return ret;
}

int varprint(variant valeur)
{
int retour;
switch(valeur.t)
{
case REEL:
retour = printf("%fn", valeur.f);
break;
case ENTIER:
retour = printf("%dn", valeur.i);
break;
case ERROR_TYPE:
case CHAINE:
retour = puts(valeur.s);
break;
default:
puts(service[VARPRINT_ERR]);
retour = -1;
}
return retour;
}

int main(void)
{
variant a = initvariant(ENTIER, "123")
, b = initvariant(REEL, "3.14159")
, c = initvariant(CHAINE, "C'est à quelle heure qu'on mange ?")
, d = initvariant(12, "Ça va pas le faire !")
, e = {ENTIER, 1234}
;
varprint(a);
varprint(b);
varprint(c);
varprint(d);
varprint(e);
e.t = REEL;
varprint(e);
e.t = -1;
varprint(e);
return 0;
}
</code>

J'ai fait un pseudo-constructeur initvariant() pour ne pas risquer l'erreur
illustrée par:
variant e = {ENTIER, 1234};
Le comportement est ici de convertir 1234 en double, mais il est
complètement lié au compilateur donc imprévisible.
Il faudrait comme en C++ pouvoir interdire l'accès direct aux données de la
structure et forcer le passage par des constructeurs, getters, setters, etc.

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.
L'union est quand même plus souple. Déjà, vous n'avez pas à vous préoccuper

de trouver le sizeof() maximum, en particulier si vous ajoutez un membre à
votre union, ou si vous aspirez à la portabilité. Vous allez vite avoir un
truc particulièrement illisible.
Vous allez transtyper à la hache du void* (ou du char*), il me semble que
vous aurez encore moins de contrôle de type qu'avec l'union.
Il y a également à faire de l'arithmétique de pointeurs sauvage un risque
"psychologique" de présumer à un moment de la taille et de l'implantation
(boutisme) des données.
Ceci dit, je me fais l'avocat de Dieu(*) (espérons qu'Il ne sera pas
ingrat), mais je suis plutôt Méphisto sur ce genre de coup.

(*) Emmanuel (de l'hébreu imanu-el ,"Dieu avec nous") est le nom sous lequel
le prophète Isaïe désigne, dans l'Ecriture sainte, le Messie ou Sauveur
promis au monde. Saint Emmanuel fut martyr en Orient.

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 ...)
On pourrait peut-être penser à des pointeurs dont le sizeof serait de 6 et

l'alignement préférentiel en modulo 8 par exemple.

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 ?
Bien entendu, ce que vous faites dépend du contexte "commercial".

Mais personnellement, s'il y a un gain réel, je n'hésiterais pas une
seconde. Vous vous couvrez en assertant.
Ici, offsetof(type_struct, b) == sizeof(my_type) et roule ma poule.
Comme ça, vos ferez avancer le schmilblic, en découvrant éventuellement
l'architecture improbable.

Avatar
Antoine Leca
En <news:432c91b5$0$21740$,
Pierre Habouzit va escriure:
Sinon *juste pour ma culture personnelle* -- je ne compte
certainement pas l'utiliser si la norme l'interdit


La norme n'interdit rien. Elle ne fait que décrire un cadre restreint, jugé
consensuel par la communauté (ce dernier fait n'est pas propre à la norme,
mais au fait qu'elle est utilisée), dans lequel le comportement est défini.
Rien ne doit t'empêcher de baguenauder en dehors de ce cadre.

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 ?


Non, en fait il est difficile d'imaginer un tel cas (où cela ne marche pas).


(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 ...)


Même dans ce cas-là, tu vas avoir du mal à ne pas satisfaire ta demande,
sauf à inventer un délire pas possible sur la manipulation des pointeurs
dans les structures.
Le plus « vraissemblable », c'est l'inverse, un compilateur qui fait exprès
de gâcher des octets entre les membres d'une structure (par exemple pour
piéger les programmes qui font des hypothèses incorrectes sur les
alignements).


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 ?


Ce n'est pas stupide, c'est seulement non portable.
Contre exemple (un peu mais pas beaucoup tiré par les cheveux, et qui
diffère un peu de ta question, mais je pense que tu sauras extrapoler): au
lieu d'entiers intéressons-nous aux flottants, dans le cadre connu de
l'architecture x86. Les plus grands flottants du microprocesseur sont les
étendus de 80 bits. Pour une cible 32 bits, il y a deux alignements
potentiellement utilisables : frontière de 16 bits (un élément tient sur 80
bits, sizeof), ou frontière de 32 bits (un élément tient sur 96 bits,
sizeof) (en 64 bits, on a aussi frontière de 64 bits, avec encore plus de
gâchis).
Je prétend que l'on peut trouver des compilateurs qui vont aligner un
tableau de ces flottants sur la frontière de 16 bits (pour économiser la
place en mémoire quand il s'agit de manipuler de grandes quantités de
données); mais par contre que les membres de structures peuvent être alignés
sur frontière de 32 bits pour accélérer les traitements sur (a priori) de
petites quantités de données. Et grâce à l'agréable propriété de
petits-boutients (l'adresse ne change pas quand on change de type), cela
reste conforme. Par contre, ta supposition ci-dessus ne tient plus.


Antoine

Avatar
Antoine Leca
En <news:432d35cb$0$9448$,
Pierre Habouzit va escriure:
Harpo wrote:

Pierre Habouzit wrote:
struct message {
[ ... pleins de choses dont pas mal de 'headers' ...];
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.


C>type struct.C
typedef int blob_t[5];

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


int main() {

hdr->scratch;
return 0;
}

C>bingcc -v -ansi struct.c
Reading specs from bin/../lib/gcc/mingw32/3.4.2/specs
[couic]
GNU C version 3.4.2 (mingw-special) (mingw32)
compiled by GNU C version 3.4.2 (mingw-special).
[couic]

struct.c:7: warning: declaration does not declare anything
struct.c: In function `main':
struct.c:13: error: structure has no member named `scratch'


Antoine



Avatar
Emmanuel Delahaye
Pierre Maurette wrote on 19/09/05 :
(*) Emmanuel (de l'hébreu imanu-el ,"Dieu avec nous") est le nom sous lequel
le prophète Isaïe désigne, dans l'Ecriture sainte, le Messie ou Sauveur
promis au monde. Saint Emmanuel fut martyr en Orient.


Comme souvent, on est pas responsable de son prénom... Tu penses faire
quoi en étalant ta science de comptoir ?


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

"There are 10 types of people in the world today;
those that understand binary, and those that dont."

Avatar
harpo
On Mon, 19 Sep 2005 20:39:55 +0200, Emmanuel Delahaye wrote:

Pierre Maurette wrote on 19/09/05 :
(*) Emmanuel (de l'hébreu imanu-el ,"Dieu avec nous") est le nom sous lequel
le prophète Isaïe désigne, dans l'Ecriture sainte, le Messie ou Sauveur
promis au monde. Saint Emmanuel fut martyr en Orient.


Comme souvent, on est pas responsable de son prénom... Tu penses faire
quoi en étalant ta science de comptoir ?


Tu montes vite au créneau !
Je n'avais rien vu de désobligeant dans sa remarque; je l'avais même
trouvé amicale.
Tu as une image sur ce forum, tout le monde en a une mais la tienne n'est
pas terne. Même si les images ne sont pas recommandées par la norme (de
mémoire Exode 20:4) nous en montrons une. Les gens sont certainement
autre chose que des images, mais la tienne est en quadrichromie. Je ne
veux pas te charrier, le fait que tu t'en offusque montre justement que
tu te ne limites pas à cette image.
Sincérement, si je n'avais pas été catholique et hétérosexuel, je
t'aurais demandé en mariage.
... Non ... là je déconnes !

--
Et les structs là-dedans ?


Avatar
Pierre Habouzit
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 ?


Ce n'est pas stupide, c'est seulement non portable.
Contre exemple (un peu mais pas beaucoup tiré par les cheveux, et qui
diffère un peu de ta question, mais je pense que tu sauras extrapoler): au
lieu d'entiers intéressons-nous aux flottants, dans le cadre connu de
l'architecture x86. Les plus grands flottants du microprocesseur sont les
étendus de 80 bits. Pour une cible 32 bits, il y a deux alignements
potentiellement utilisables : frontière de 16 bits (un élément tient sur
80 bits, sizeof), ou frontière de 32 bits (un élément tient sur 96
bits, sizeof) (en 64 bits, on a aussi frontière de 64 bits, avec encore
plus de gâchis).
Je prétend que l'on peut trouver des compilateurs qui vont aligner un
tableau de ces flottants sur la frontière de 16 bits (pour économiser la
place en mémoire quand il s'agit de manipuler de grandes quantités de
données); mais par contre que les membres de structures peuvent être
alignés sur frontière de 32 bits pour accélérer les traitements sur (a
priori) de petites quantités de données. Et grâce à l'agréable propriété
de petits-boutients (l'adresse ne change pas quand on change de type),
cela reste conforme. Par contre, ta supposition ci-dessus ne tient plus.


ok, merci pour les infos.
--
·O· Pierre Habouzit
··O
OOO http://www.madism.org


Avatar
Pierre Habouzit
C>type struct.C
typedef int blob_t[5];

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


int main() {

hdr->scratch;
return 0;
}

C>bingcc -v -ansi struct.c
Reading specs from bin/../lib/gcc/mingw32/3.4.2/specs
[couic]
GNU C version 3.4.2 (mingw-special) (mingw32)
compiled by GNU C version 3.4.2 (mingw-special).
[couic]

struct.c:7: warning: declaration does not declare anything
struct.c: In function `main':
struct.c:13: error: structure has no member named `scratch'


ah tiens, c'est donc une extension GCC ...
hmmmmm

bon, /me note qu'il a du code pas portable à corriger.
crotte.
--
·O· Pierre Habouzit
··O
OOO http://www.madism.org

Avatar
Pierre Maurette
Pierre Maurette wrote on 19/09/05 :
(*) Emmanuel (de l'hébreu imanu-el ,"Dieu avec nous") est le nom sous
lequel le prophète Isaïe désigne, dans l'Ecriture sainte, le Messie ou
Sauveur promis au monde. Saint Emmanuel fut martyr en Orient.


Comme souvent, on est pas responsable de son prénom...
Certes, mais le choix d'une identité sur usenet est en quelque sorte

une seconde naissance, non ? J'ai également fait le choix courageux de
la transparence, moi qui suis Pierre sur laquelle Tu bâtis naguère Ton
Église.

Tu penses faire quoi
en étalant ta science de comptoir ?
Pas science, juste Google.


--
Pierre Maurette


1 2