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

Taille de classe

32 réponses
Avatar
Lucas Levrel
Bonjour,

Si une classe A contient un champ de type T, est-il garanti que sizeof(A)
sera un multiple entier de sizeof(T) ?

Par exemple, si je définis
class A {
int un;
double deux;
}
(ou double d'abord et int ensuite) et que je compile avec le compilateur
Intel, j'obtiens sizeof(A)==16 (sachant que sizeof(int)==4 et
sizeof(double)==8). Est-ce imposé par la norme ? Est-ce un comportement
général des compilateurs courants ?

--
LL

10 réponses

1 2 3 4
Avatar
espie
In article , Benoit Izac wrote:
Je comprends le problème pour les structures (ça a été discuté dans fclc
il y a quelques temps). En revanche, ne connaissant pas C++, je me dis
qu'il doit y avoir une raison pour choisir un classe plutôt qu'une
structure.



Benoit, je t'aime bien, mais dans ce cas-la precise que tu es un gros
debutant, ca evitera de rajouter du bruit...

Alors, voila. class et struct, pour des definitions de structure, c'est
la meme chose en C++. Le seul point qui change, c'est la visibilite
par defaut des membres de l'agregat (avec class, ils sont private. Avec
struct, ils sont publics).

Cote layout memoire, c'est comme du C, dans la mesure ou ca a un sens!
Il y a une description assez simple dans la norme C++98, et un peu
plus complexe (nettement) dans la norme C++2011.

En simplifiant, l'esprit c'est que tu n'es pas cense payer plus cher en
C++ qu'en C. Du coup, s'il n'y a pas de fonctionnalite supplementaire
du C++, qui necessite une vtable, une structure C++ va faire exactement
la meme taille que la structure equivalente C, et avoir le meme layout.

Si ta classe n'a que des champs fonction non polymorphes (rien de virtual),
alors elle n'a rien besoin de stocker pour ceux-ci. Des qu'il y a du
polymorphisme (virtual), il faut des infos cachees pour retrouver les
methodes. Traditionnellement, vtable... ca se complique en presence
d'heritage, avec plein, plein d'optimisations sophistiquees sur les
implementations recentes.

Dans la nouvelle norme, on detaille en plus le processus de construction
des instances, sachant qu'une struct "sans rien" se construit et se copie
a la main, mais que ca se complique des qu'il y a constructeurs ou destructeurs.

Plus exactement, la nouvelle norme explique plus en detail:
- les cas ou le layout d'une instance C++ est le meme que pour la struct C
equivalente.
- les cas ou on peut joyeusement copier des objets d'un endroit a l'autre
bit-a-bit, a la meme copie.

Une 'chtite particularite du C++, c'est la tres fameuse
"Empty base optimisation".

Comme en C, une struct, meme vide, DOIT faire au moins un octet.

Du coup, si on fait des trucs composites, genre:

struct A {};

struct B {
A a;
...
};

alors le champs a doit occuper au moins un octet.

Alors que si on fait pareil en heritant:
struct Bp: public A {
...
};

ben la classe de base A peut totalement etre optimisee.

(evidemment, une struct sans rien ne sert a rien... ca va etre utile uniquement
pour recuperer les fonctions membres qui vont avec).


Voila pour un petit resume sommaire des differences saillantes avec C
cote disposition memoire.
Avatar
Benoit Izac
Bonjour,

le 05/10/2012 à 23:00, Marc Espie a écrit dans le message
<k4nhp6$2mh0$ :

Je comprends le problème pour les structures (ça a été discuté dans fclc
il y a quelques temps). En revanche, ne connaissant pas C++, je me dis
qu'il doit y avoir une raison pour choisir un classe plutôt qu'une
structure.



Benoit, je t'aime bien, mais dans ce cas-la precise que tu es un gros
debutant, ca evitera de rajouter du bruit...

Alors, voila. [...]



Merci pour les explications bien que ma culture C++ ne me permette pas
d'en comprendre la totalité. Notamment pour les vtables, je n'ai pas
encore écris mon propre compilateur "from scratch". ;-)

--
Benoit Izac
Avatar
Lucas Levrel
Le 5 octobre 2012, Alain Ketterlin a écrit :

class A {
int un;
double deux;
}





Tu veux sûrement dire la taille des instances ? Je ne pense pas que ce
soit la taille en soi qui intéresse Lucas, mais plutôt la fragmentation
induite par l'ordre et l'alignement des champs.



Les deux en fait. J'ai un tableau d'A :
A tab[1000];
et je veux en extraire un tableau d'A.deux. Donc je définis
double *ptr=&(tab->deux);
puis je fais une boucle qui incrémente
ptr += sizeof(A)/sizeof(double);

(Ou j'utilise une fonction de bibliothèque à laquelle je passe le
« stride » sizeof(A)/sizeof(double).)

--
LL
Avatar
Olivier Miakinen
Bonjour,

Le 08/10/2012 11:06, Lucas Levrel a écrit :

class A {
int un;
double deux;
}







J'ai un tableau d'A :
A tab[1000];
et je veux en extraire un tableau d'A.deux. Donc je définis
double *ptr=&(tab->deux);
puis je fais une boucle qui incrémente
ptr += sizeof(A)/sizeof(double);



C'est un peu casse-gueule, tu ne crois pas ? Alors qu'il serait si
simple d'avoir un pointeur sur A, que tu incrémentes normalement,
avant d'en extraire le deux à chaque tour de boucle.

Plus précisément, je pense que ta technique doit fonctionner, peut-être
même la norme te donnera-t-elle l'assurance que ça fonctionnera partout
(à vérifier tout de même), mais si toi ou quelqu'un d'autre se trouve
devoir débuguer ce code dans quelques mois ou quelques années ça risque
d'être difficile à relire.

Cordialement,
--
Olivier Miakinen
Avatar
Jean-Marc Bourguet
Olivier Miakinen <om+ writes:

Bonjour,

Le 08/10/2012 11:06, Lucas Levrel a écrit :

class A {
int un;
double deux;
}







J'ai un tableau d'A :
A tab[1000];
et je veux en extraire un tableau d'A.deux. Donc je définis
double *ptr=&(tab->deux);
puis je fais une boucle qui incrémente
ptr += sizeof(A)/sizeof(double);





Tant qu'à jouer à bas niveau, j'aurais tendence à faire

ptr = (double*)(((char*)ptr) + sizeof(A));

au moins je ne vois pas de problèmes (cast de et vers char* ne pose pas
de problème d'alias, ajouter sizeof(A) puis recaster en double* fonctionne
même si le ppcm de l'alignement d'un double et d'un int est plus petit que
la taille d'un double)

C'est un peu casse-gueule, tu ne crois pas ? Alors qu'il serait si
simple d'avoir un pointeur sur A, que tu incrémentes normalement,
avant d'en extraire le deux à chaque tour de boucle.



Je suppose que sa fonction prend un déplacement et un pointeur vers
double. Lui faire comprendre qu'elle peut avoir des A nécessiterait soit de
la dupliquer pour tous les types possibles, soit en faire un template de
fonctions (ce est qui la meilleure solution en C++ pour l'interface, quite
à ce que l'implémentation du template fasse suivre à une fonction partagée
entre les instances si la duplication est réellement un problème).

Plus précisément, je pense que ta technique doit fonctionner, peut-être
même la norme te donnera-t-elle l'assurance que ça fonctionnera partout
(à vérifier tout de même), mais si toi ou quelqu'un d'autre se trouve
devoir débuguer ce code dans quelques mois ou quelques années ça risque
d'être difficile à relire.



Il me semble que ça a été dit, il n'est en rien garanti que sizeof(A) soit
un multiple entier de sizeof(double). (Un int de 4 bytes et un double
aligné sur 4 bytes ne me semble pas une absurdité, en fait je serais même
surpris qu'aucun compilateur n'ait jamais utilisé ce mode).

A+

--
Jean-Marc
FAQ de fclc++: http://web.archive.org/web/*/http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Alain Ketterlin
Jean-Marc Bourguet writes:

class A {
int un;
double deux;
}







J'ai un tableau d'A :
A tab[1000];
et je veux en extraire un tableau d'A.deux. Donc je définis
double *ptr=&(tab->deux);
puis je fais une boucle qui incrémente
ptr += sizeof(A)/sizeof(double);





ptr = (double*)(((char*)ptr) + sizeof(A));

C'est un peu casse-gueule, tu ne crois pas ? Alors qu'il serait si
simple d'avoir un pointeur sur A, que tu incrémentes normalement,
avant d'en extraire le deux à chaque tour de boucle.



Je suppose que sa fonction prend un déplacement et un pointeur vers
double. Lui faire comprendre qu'elle peut avoir des A nécessiterait soit de
la dupliquer pour tous les types possibles, soit en faire un template de
fonctions (ce est qui la meilleure solution en C++ pour l'interface, quite
à ce que l'implémentation du template fasse suivre à une f onction partagée
entre les instances si la duplication est réellement un problèm e).



C'est cela qu'il faut faire, à mon avis. Des templates pour chaque cas,
spécialisés sur le type (A ici). C'est tellement petit ces calculs
d'adresse (et constant) que le compilo va inliner tout ça. Et s'il
n'inline pas, ça veut dire qu'on ne veut pas optimiser, et donc autant
avoir quelque chose de type-safe.

Plus précisément, je pense que ta technique doit fonctionner, peut-être
même la norme te donnera-t-elle l'assurance que ça fonctionner a partout
(à vérifier tout de même), mais si toi ou quelqu'un d'aut re se trouve
devoir débuguer ce code dans quelques mois ou quelques années ça risque
d'être difficile à relire.



Il me semble que ça a été dit, il n'est en rien garanti qu e sizeof(A) soit
un multiple entier de sizeof(double). (Un int de 4 bytes et un double
aligné sur 4 bytes ne me semble pas une absurdité, en fait je s erais même
surpris qu'aucun compilateur n'ait jamais utilisé ce mode).



Ce qu'on a dit, c'est que sizeof(A) est un multiple entier du plus grand
des champs (parce que celui-ci doit être aligné, y compris dans un
tableau). Et le plus grand a l'air d'être un double. Je ne connais pas
d'archi ou sizeof(double) < sizeof(int), mais pourquoi pas...

-- Alain.
Avatar
Olivier Miakinen
Le 08/10/2012 15:25, Jean-Marc Bourguet a écrit :

class A {
int un;
double deux;
}







J'ai un tableau d'A :
A tab[1000];
et je veux en extraire un tableau d'A.deux. Donc je définis
double *ptr=&(tab->deux);
puis je fais une boucle qui incrémente
ptr += sizeof(A)/sizeof(double);





Tant qu'à jouer à bas niveau, j'aurais tendance à faire

ptr = (double*)(((char*)ptr) + sizeof(A));



Oui. C'est un chouia plus lisible... ou du moins c'est plus courant.

au moins je ne vois pas de problèmes (cast de et vers char* ne pose pas
de problème d'alias, ajouter sizeof(A) puis recaster en double* fonctionne



Oui.

même si le ppcm de l'alignement d'un double et d'un int est plus petit que
la taille d'un double)



Parce qu'un double pourrait être aligné sur moins que sa taille ?
Par exemple il pourrait avoir une taille de 8 octets et être aligné
sur 4 ? Quoi qu'il en soit, si c'est le cas le calcul de ptr ci-dessus
donnera une adresse valide pour un pointeur vers double, donc je ne
vois pas non plus de problème (sauf de lisibilité, quand même un petit
peu...)

C'est un peu casse-gueule, tu ne crois pas ? Alors qu'il serait si
simple d'avoir un pointeur sur A, que tu incrémentes normalement,
avant d'en extraire le deux à chaque tour de boucle.



Je suppose que sa fonction prend un déplacement et un pointeur vers
double.



Ok, je comprends.

Lui faire comprendre qu'elle peut avoir des A nécessiterait soit de
la dupliquer pour tous les types possibles, soit en faire un template de
fonctions (ce est qui la meilleure solution en C++ pour l'interface, quite
à ce que l'implémentation du template fasse suivre à une fonction partagée
entre les instances si la duplication est réellement un problème).



Ok.

Plus précisément, je pense que ta technique doit fonctionner, peut-être
même la norme te donnera-t-elle l'assurance que ça fonctionnera partout
(à vérifier tout de même), mais si toi ou quelqu'un d'autre se trouve
devoir débuguer ce code dans quelques mois ou quelques années ça risque
d'être difficile à relire.



Il me semble que ça a été dit, il n'est en rien garanti que sizeof(A) soit
un multiple entier de sizeof(double). (Un int de 4 bytes et un double
aligné sur 4 bytes ne me semble pas une absurdité, en fait je serais même
surpris qu'aucun compilateur n'ait jamais utilisé ce mode).



Je suis désolé si je n'ai pas tout lu (ou pas tout compris) de ce qui
s'est dit précédemment. Je découvre qu'on pourrait avoir un type simple
donc la taille est plus grande que son alignement, chose que j'ignorais.

Cordialement,
--
Olivier Miakinen
Avatar
Olivier Miakinen
Le 08/10/2012 16:58, Alain Ketterlin a écrit :

Il me semble que ça a été dit, il n'est en rien garanti que sizeof(A) soit
un multiple entier de sizeof(double). (Un int de 4 bytes et un double
aligné sur 4 bytes ne me semble pas une absurdité, en fait je serais même
surpris qu'aucun compilateur n'ait jamais utilisé ce mode).



Ce qu'on a dit, c'est que sizeof(A) est un multiple entier du plus grand
des champs (parce que celui-ci doit être aligné, y compris dans un
tableau). Et le plus grand a l'air d'être un double.



Un multiple entier du plus grand des champs, ou un multiple entier du
plus grand des alignements requis ?

Je ne connais pas
d'archi ou sizeof(double) < sizeof(int), mais pourquoi pas...



;-)

Pourquoi pas, en effet, mais ce n'est pas là que serait le problème.

Il y aurait problème si on avait sizeof(int) = 4, sizeof(double) = 8,
mais alignement(int) = alignement(double) = 4. Dans ce cas, la taille
de A serait de 12 octets, et sizeof(A)/sizeof(double) vaudrait 3/2.
Avatar
Jean-Marc Bourguet
Olivier Miakinen <om+ writes:

Le 08/10/2012 16:58, Alain Ketterlin a écrit :

Il me semble que ça a été dit, il n'est en rien garanti que sizeof(A) soit
un multiple entier de sizeof(double). (Un int de 4 bytes et un double
aligné sur 4 bytes ne me semble pas une absurdité, en fait je serais même
surpris qu'aucun compilateur n'ait jamais utilisé ce mode).



Ce qu'on a dit, c'est que sizeof(A) est un multiple entier du plus grand
des champs (parce que celui-ci doit être aligné, y compris dan s un
tableau). Et le plus grand a l'air d'être un double.



Un multiple entier du plus grand des champs, ou un multiple entier du
plus grand des alignements requis ?



Multiple entier du ppcm des alignements. (En pratique, je n'ai jamais vu
de contraintes d'alignement qui ne soit pas des puissances de 2, meme si
je suis a peu pres sur d'avoir vu une telle contrainte proposee pour les
instructions, en pratique donc multiple entier du plus grand des
alignements).

Je ne connais pas d'archi ou sizeof(double) < sizeof(int), mais
pourquoi pas...



;-)

Pourquoi pas, en effet, mais ce n'est pas là que serait le problà ¨me.

Il y aurait problème si on avait sizeof(int) = 4, sizeof(double) = 8,
mais alignement(int) = alignement(double) = 4. Dans ce cas, la taille
de A serait de 12 octets, et sizeof(A)/sizeof(double) vaudrait 3/2.



Je pensais a ce cas. Il y a deux types de contraintes d'alignements.
Celles imposees (si on ne les respecte pas, ca crache). C'est
generalement la taille du type, mais dans le cas de types vectoriel, la
taille de l'element est un meilleur choix.

Puis celles pour des raisons de perf sur des archi qui n'ont pas de
contraintes dures. Ca va dependre pas mal de l'archi. La tendence
actuelle, c'est "tant que c'est dans une ligne de cache, ca ne change
pas les perfs". Pour les machines sans cache, ce qui compte, c'est
plutot le nombre d'acces a la memoire. Dans cette optique, avec un bus
donnee de 32 bits, tant que le double est aligne sur 4 bytes, ca ne
change pas le nombre de transfert necessaire, donc c'est une contrainte
potentielle. Puis on a des frontieres plus haut qui comme les lignes de
caches peuvent etre couteuses a franchir. Comme ces frontieres ne
traversent pas les lignes de cache, on en tient compte pour des donnees
plus grandes, et generalement uniquement a la main.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index. html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Lucas Levrel
Le 8 octobre 2012, Olivier Miakinen a écrit :

C'est un peu casse-gueule, tu ne crois pas ? Alors qu'il serait si
simple d'avoir un pointeur sur A, que tu incrémentes normalement,
avant d'en extraire le deux à chaque tour de boucle.



Je pense que ma façon de faire résulte en moins d'instructions, mais je
n'ai pas vérifié sur le code assemblé (bouh !).

--
LL
1 2 3 4