Objects and alignment
Les programmes C créent, détruisent, accèdent et manipulent des objets.
Un objet en C est une région de stockage de données dans l'environnement d'exécution, dont le contenu peut représenter des valeurs (une valeur est la signification du contenu d'un objet, lorsqu'il est interprété comme ayant un type spécifique).
Chaque objet possède
-
taille (peut être déterminée avec
sizeof) -
exigence d'alignement
(peut être déterminée par
_Alignof(jusqu'à C23)alignof(depuis C23) ) (depuis C11) - durée de stockage (automatique, statique, allouée, locale au thread)
- durée de vie (égale à la durée de stockage ou temporaire)
- type effectif (voir ci-dessous)
- valeur (qui peut être indéterminée)
- optionnellement, un identifiant qui désigne cet objet.
Les objets sont créés par déclarations , fonctions d'allocation , littéraux de chaîne , littéraux composés , et par des expressions non-lvalue qui renvoient des structures ou unions avec des membres de tableau .
Table des matières |
Représentation d'objet
Sauf pour les
champs de bits
, les objets sont composés de séquences contiguës d'un ou plusieurs octets, chacun constitué de
CHAR_BIT
bits, et peuvent être copiés avec
memcpy
dans un objet de type
unsigned
char
[
n
]
, où
n
est la taille de l'objet. Le contenu du tableau résultant est appelé
représentation d'objet
.
Si deux objets ont la même représentation objet, ils sont égaux (sauf s'il s'agit de NaN flottants). L'inverse n'est pas vrai : deux objets qui sont égaux peuvent avoir des représentations objet différentes car tous les bits de la représentation objet ne participent pas nécessairement à la valeur. Ces bits peuvent être utilisés pour le remplissage afin de satisfaire les exigences d'alignement, pour les contrôles de parité, pour indiquer des représentations de piège, etc.
Si la représentation d'un objet ne représente aucune valeur du type de l'objet, elle est appelée trap representation . Accéder à une trap representation de toute autre manière que par sa lecture via une expression lvalue de type caractère est un comportement indéfini. La valeur d'une structure ou d'une union n'est jamais une trap representation même si un membre particulier en est une.
Pour les objets de type char , signed char , et unsigned char , chaque bit de la représentation de l'objet doit participer à la représentation de la valeur et chaque motif de bits possible représente une valeur distincte (pas de remplissage, de bits piégés ou de représentations multiples autorisées).
Lorsque les objets de types entiers ( short , int , long , long long ) occupent plusieurs octets, l'utilisation de ces octets est définie par l'implémentation, mais les deux implémentations dominantes sont big-endian (POWER, Sparc, Itanium) et little-endian (x86, x86_64) : une plateforme big-endian stocke l'octet le plus significatif à l'adresse la plus basse de la région de stockage occupée par l'entier, une plateforme little-endian stocke l'octet le moins significatif à l'adresse la plus basse. Consultez Endianness pour plus de détails. Voir aussi l'exemple ci-dessous.
Bien que la plupart des implémentations n'autorisent pas les représentations piégées, les bits de remplissage, ou les représentations multiples pour les types entiers, il existe des exceptions ; par exemple, une valeur d'un type entier sur Itanium peut être une représentation piégée .
Type effectif
Chaque objet a un type effectif , qui détermine quels lvalue accès sont valides et lesquels violent les règles de strict aliasing.
Si l'objet a été créé par une déclaration , le type déclaré de cet objet est le type effectif de l'objet.
Si l'objet a été créé par une fonction d'allocation (y compris realloc ), il n'a pas de type déclaré. Un tel objet acquiert un type effectif comme suit :
- La première écriture dans cet objet via une lvalue qui a un type autre qu'un type caractère, moment auquel le type de cette lvalue devient le type effectif de cet objet pour cette écriture et toutes les lectures ultérieures.
- memcpy ou memmove copient un autre objet dans cet objet, ou copient un autre objet dans cet objet comme un tableau de type caractère, moment auquel le type effectif de l'objet source (s'il en avait un) devient le type effectif de cet objet pour cette écriture et toutes les lectures ultérieures.
- Tout autre accès à l'objet sans type déclaré, le type effectif est le type de la lvalue utilisée pour l'accès.
Aliasing strict
Étant donné un objet avec un type effectif T1, utiliser une expression lvalue (généralement, le déréférencement d'un pointeur) d'un type différent T2 est un comportement indéfini, sauf si :
- T2 et T1 sont des types compatibles .
- T2 est une version qualifiée cvr d'un type compatible avec T1.
- T2 est une version signée ou non signée d'un type compatible avec T1.
- T2 est un type agrégat ou un type union qui inclut l'un des types mentionnés ci-dessus parmi ses membres (y compris, récursivement, un membre d'un sous-agrégat ou d'une union contenue).
- T2 est un type caractère ( char , signed char , ou unsigned char ).
Ces règles déterminent si, lors de la compilation d'une fonction qui reçoit deux pointeurs, le compilateur doit émettre du code qui relit l'un après avoir écrit via l'autre :
// int* et double* ne peuvent pas être des alias void f1(int* pi, double* pd, double d) { // la lecture de *pi peut être effectuée une seule fois, avant la boucle for (int i = 0; i < *pi; i++) *pd++ = d; }
struct S { int a, b; }; // int* et struct S* peuvent aliasser car S est un type agrégé avec un membre de type int void f2(int* pi, struct S* ps, struct S s) { // la lecture de *pi doit avoir lieu après chaque écriture via *ps for (int i = 0; i < *pi; i++) *ps++ = s; }
Notez que le qualificateur restrict peut être utilisé pour indiquer que deux pointeurs ne sont pas des alias même si les règles ci-dessus le permettent.
Notez que le type-punning peut également être effectué via le membre inactif d'une union .
Alignement
Chaque type d'objet complet possède une propriété appelée exigence d'alignement , qui est une valeur entière de type size_t représentant le nombre d'octets entre les adresses successives auxquelles les objets de ce type peuvent être alloués. Les valeurs d'alignement valides sont des puissances entières non négatives de deux.
|
L'exigence d'alignement d'un type peut être interrogée avec
|
(depuis C11) |
Afin de satisfaire les exigences d'alignement de tous les membres d'une structure, un remplissage peut être inséré après certains de ses membres.
#include <stdalign.h> #include <stdio.h> // les objets de la struct S peuvent être alloués à n'importe quelle adresse // car S.a et S.b peuvent être alloués à n'importe quelle adresse struct S { char a; // taille : 1, alignement : 1 char b; // taille : 1, alignement : 1 }; // taille : 2, alignement : 1 // les objets de la struct X doivent être alloués sur des limites de 4 octets // car X.n doit être alloué sur des limites de 4 octets // car l'exigence d'alignement de int est (généralement) 4 struct X { int n; // taille : 4, alignement : 4 char c; // taille : 1, alignement : 1 // trois octets de remplissage }; // taille : 8, alignement : 4 int main(void) { printf("sizeof(struct S) = %zu\n", sizeof(struct S)); printf("alignof(struct S) = %zu\n", alignof(struct S)); printf("sizeof(struct X) = %zu\n", sizeof(struct X)); printf("alignof(struct X) = %zu\n", alignof(struct X)); }
Sortie possible :
sizeof(struct S) = 2 alignof(struct S) = 1 sizeof(struct X) = 8 alignof(struct X) = 4
Chaque type d'objet impose son exigence d'alignement sur chaque objet de ce type. L'alignement le plus faible (le plus petit) est l'alignement des types char , signed char , et unsigned char , et est égal à 1 . L'alignement fondamental le plus strict (le plus grand) de tout type est défini par l'implémentation et égal à l'alignement de max_align_t (depuis C11) .
Les alignements fondamentaux sont pris en charge pour les objets de toutes les durées de stockage.
|
Si l'alignement d'un objet est rendu plus strict (plus grand) que
max_align_t
en utilisant
Si un type struct ou union
La version atomique de chaque type arithmétique ou pointeur a un alignement fondamental. |
(depuis C11) |
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C publiées antérieurement.
| DR | Application | Comportement publié | Comportement corrigé |
|---|---|---|---|
| DR 445 | C11 | un type pourrait avoir un alignement étendu sans _Alignas impliqué | il doit avoir un alignement fondamental |
Références
- Norme C17 (ISO/CEI 9899:2018) :
-
- 3.15 objet (p: 5)
-
- 6.2.6 Représentations des types (p: 33-35)
-
- 6.2.8 Alignement des objets (p: 36-37)
-
- 6.5/6-7 Expressions (p: 55-56)
- Norme C11 (ISO/IEC 9899:2011) :
-
- 3.15 object (p: 6)
-
- 6.2.6 Representations of types (p: 44-46)
-
- 6.2.8 Alignment of objects (p: 48-49)
-
- 6.5/6-7 Expressions (p: 77)
- Norme C99 (ISO/CEI 9899:1999) :
-
- 3.2 alignement (p: 3)
-
- 3.14 objet (p: 5)
-
- 6.2.6 Représentations des types (p: 37-39)
-
- 6.5/6-7 Expressions (p: 67-68)
- Norme C89/C90 (ISO/CEI 9899:1990) :
-
- 1.6 Définitions des termes
Voir aussi
|
Documentation C++
pour
Object
|