Pourquoi la taille de la structure A n’est pas égale à la taille de la structure B avec les mêmes champs?

Pourquoi la taille de la struct A n’est pas égale à la taille de la struct B ?

Et ce que je dois faire, ils auront la même taille?

 using System; namespace ConsoleApplication1 { class Program { struct A { char a; char c; int b; } struct B { char a; int b; char c; } static void Main(ssortingng[] args) { unsafe { Console.WriteLine(sizeof(A)); Console.WriteLine(sizeof(B)); } Console.ReadLine(); } } } 

La sortie est:

 8 12 

Il y a des rembourrages parmi les champs. Le rembourrage est calculé à l’aide des champs précédents et suivants.

En outre, cette condition devrait être vraie:

 (size of struct) % (size of largest type) == 0 

Dans votre cas, le type le plus grand est int et sa taille est de 4 octets.

 struct A { char a; // size is 2, no previous field, next field size is 2 - no alignment needed char c; // size is 2, previous size is 2 -> 2 + 2 = 4, next size is 4 - no alignment needed int b; //size is 4, it is last field, size is 4 + 4 = 8. //current size is 2 + 2 + 4 = 8 //8 % 4 == 0 - true - 8 is final size } struct B { char a; // size is 2, next size is 4, alignment needed - 2 -> 4, size of this field with alignment is 4 int b; // size is 4, previous is 4, next size is 2(lower) - no alignment needed char c; // size is 2, previous is 4 + 4 = 8 - no alignment needed //current size is 4 + 4 + 2 = 10 //but size should be size % 4 = 0 -> 10 % 4 == 0 - false, adjust to 12 } 

Si vous voulez la même taille pour deux structures, vous pouvez utiliser LayoutKind.Explicit :

 [StructLayout(LayoutKind.Explicit)] public struct A { [FieldOffset(0)] char a; [FieldOffset(2)] char c; [FieldOffset(4)] int b; } [StructLayout(LayoutKind.Explicit)] public struct B { [FieldOffset(0)] char a; [FieldOffset(2)] int b; [FieldOffset(6)] char c; } 

OU

Vous pouvez utiliser LayoutKind.Sequential , Pack = 1 et CharSet = CharSet.Unicode pour obtenir la taille 8.

 [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] public struct A { char a; char c; int b; } [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] public struct B { char a; int b; char c; } 

En outre, vous pouvez obtenir une taille de structure sans unsafe :

 Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(typeof(A))); Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(typeof(B))); 

C’est parce que votre compilateur se réserve le droit d’insérer un padding entre les membres de la struct , plus un espace à la fin. (Notez cependant que le remplissage n’est pas autorisé avant le premier membre.)

Cela permet d’aligner le début des membres sur des emplacements de mémoire facilement adressables.

En particulier, un compilateur est susceptible d’insérer un remplissage entre un seul caractère et un int . Un nombre pair de caractères suivi d’un int peut donc occuper moins d’espace qu’un caractère suivi d’un int suivi d’un nombre impair de caractères.

Il s’agit d’un détail de l’implémentation du processeur, que .NET s’efforce de cacher. Les variables doivent avoir un emplacement de stockage permettant au processeur de lire et d’écrire la valeur avec une seule opération de bus de données. Cela rend l’ alignement de l’adresse de la variable très important. La lecture d’un seul octet n’est jamais un problème. Mais un court (2 octets) doit avoir une adresse qui est un multiple de 2. Un int (4 octets) doit avoir une adresse un multiple de 4. Idéalement, un long ou un double (8 octets) doit avoir une adresse multiple de 8, mais cela ne peut pas toujours être réalisé, pas sur un processeur 32 bits.

Les processeurs Intel et AMD permettent des lectures et des écritures non alignées, contrairement aux cœurs RISC. Mais cela peut avoir un coût, il faut parfois deux cycles de bus de données pour lire deux morceaux d’octets, une partie des octets supérieurs d’une valeur et une partie des octets inférieurs. Avec un circuit qui mélange ces octets au bon endroit. Cela prend du temps, généralement 1 à 3 cycles d’horloge supplémentaires. Beaucoup de temps sur un kernel RISC pour gérer le piège d’erreur de bus.

Mais plus sévèrement, il casse le modèle de mémoire .NET. Il fournit une garantie d’atomicité pour les types de valeur simples et les références d’object. Unaligned lit et écrit rompre cette promesse. Cela pourrait causer des déchirures , en observant une partie des octets en cours d’écriture. Et bien pire, cela peut casser le ramasse-miettes. Le GC dépend fortement de la mise à jour atomique d’une référence d’object.

Ainsi, lorsque le CLR détermine la disposition d’une structure ou d’une classe, il doit s’assurer que les exigences d’alignement sont satisfaites. Et si ce n’est pas le cas, il doit laisser un espace inutilisé supplémentaire entre les variables. Et peut-être un espace supplémentaire à la fin pour vous assurer que les membres sont toujours alignés lorsqu’ils sont stockés dans un tableau. Le mot générique pour cet espace supplémentaire est “padding”.

Spécifique à une déclaration de classe, il a [StructLayout (LayoutKind.Auto)], il peut mélanger les membres pour obtenir la meilleure présentation. Pas une structure, ils sont LayoutKind.Sequential par défaut. Au-delà des classes et des structures, cette garantie d’alignement est également requirejse pour les variables statiques ainsi que pour les arguments et les variables locales d’une méthode. Mais pas aussi facilement observé.

L’ordre des champs est différent. Je suppose que la taille est différente car les membres sont matelassés (c’est-à-dire qu’ils commencent par un mot machine identique afin de faciliter l’access au désortingment de la consommation de mémoire).