/* en C90 */
 printf("L'offset de 'champ2' vaut %lu.\n",
 (unsigned long) offsetof(struct ma_structure, champ2));
 return 0;
Sur une architecture 32 bits, vous obtiendrez très certainement la réponse:
En fait toute cette section était pour souligner le fait qu'il est difficilement portable de comparer les structures comme des blocs binaires (via la fonction codice_28 par exemple), car il n'y a aucune garantie que ces octets de bourrage soient initialisés à une certaine valeur. De la même manière, il est sage de prendre quelques précautions avant de transférer cette structure à l'extérieur du programme (comme un fichier, un tube de communication ou une socket IP). En général, il préférable de traiter la structure champ par champ, pour ce genre d'opérations.
Mais comme rien n'est toujours totalement négatif, les conséquences du bourrage offre énormément de souplesse :
imaginons 2 structures:
struct str {
 char *string;
struct str_ok {
 char *string;
 size_t len;
quelle que soit la structure utilisé, on peut avec l'adresse et la cast (struct str *) accéder à la chaine.
int main (void)
 struct str s = { "chaine" };
 struct str_ok sok = { "chaine", 0 };
 void *p = &sok;
 if (((struct str *)p)->string)
 sok.len = strlen(sok.string);
 printf("%s\n", ((struct str *)p)->string);
 /* en revanche: Peut-être très dangereux ! */
 p = &s;
 printf("%s\n",((struct str_ok *)p)->string);
 return 0;
Pointeurs vers structures.
Il est (bien entendu) possible de déclarer des variables de type pointeur vers structure :
struct ma_struct * ma_variable;
Comme pour tout pointeur, on doit allouer de la mémoire pour la variable avant de l'utiliser :
ma_variable = malloc( sizeof(struct ma_struct) );
L'accès aux champs peut se faire comme pour une variable de type structure « normale » :
(* ma_variable).champ
Ce cas de figure est en fait tellement fréquent qu'il existe un raccourci pour l'accès aux champs d'un pointeur vers structure :
ma_variable->champ
Unions.
Une union et un enregistrement se déclarent de manière identique :
union type_union
 type1 champ1;
 type2 champ2;
 typeN champN;
/* Déclaration de variables */
union type_union var1, var2, /* ... */ varM;
Toutefois, à la différence d'un enregistrement, les N champs d'une instance de cette union occupent le même emplacement en mémoire. Modifier l'un des champ modifie donc tous les champs de l'union. Typiquement, une union s'utilise lorsqu'un enregistrement peut occuper plusieurs fonctions bien distinctes et que chaque fonction ne requiert pas l'utilisation de tous les champs.
Voici un exemple ultra classique d'utilisation des unions, qui provient en fait de la gestion des événements du système X-Window, très répandu sur les systèmes Unix :
union _XEvent
 int type;
 XAnyEvent xany;
 XKeyEvent xkey;
 XButtonEvent xbutton;
 XMotionEvent xmotion;
 XCrossingEvent xcrossing;
 XFocusChangeEvent xfocus;
 XExposeEvent xexpose;
 XErrorEvent xerror;
 XKeymapEvent xkeymap;
 long pad[24];
La déclaration a juste été un peu raccourcie pour rester lisible. Les types codice_29 de l'union sont en fait des structures contenant des champs spécifiques au message. Par exemple, si codice_27 vaut codice_31, seules les valeurs du champ codice_32 seront significatives. Parmi ces champs, il y a codice_33 qui permet de savoir quel bouton de la souris à été pressé :
 XEvent ev;
 XNextEvent(display, &ev);
 switch (ev.type) {
 case ButtonPress:
 printf("Le bouton %d a été pressé.\n", ev.xbutton.button);
 break;
 default:
 printf("Message type %d\n", ev.type);
En offrant une souplesse nécessaire à la manipulation de l'objet, la gestion de la mémoire en C peut aider à réduire les erreurs de programmation lié à la structure de la mémoire (erreur de segmentation/segfault/"Argh!!!"):
struct s1 {
 char *string;
} s1;
struct s2 {
 char *string;
 size_t len;
 int ______;
 int setnull;
} s2;
union str {
 struct s2 s_;
 struct s1 _s;
}str;
typedef void * STR_s1;
typedef void * STR_s2;
STR_s2
string_analyse(STR_s1 *s)
 static union str us, init_us = ;
 memcpy((void *)&us, (void *)&init_us, sizeof(union str));
 if (!((struct s1 *)s)->string)
 return &us._s;
 us.s_.len = strlen(((struct s1 *)s)->string);
 if (!us.s_.len)
 us.s_.setnull = 1;
 return &us._s;
 us.s_.string = ((struct s1 *)s)->string;
 return &us.s_;
int main(void)
 struct s1 s = {NULL}, s_ = {"salut"}, s__ = {""};
 void *p;
 p = string_analyse((void *)&s);
 printf("null? %s,%lu,%i\n",
 ((struct s2 *)p)->string, ((struct s2 *)p)->len, ((struct s2 *)p)->setnull);
 p = string_analyse((void *)&s_);
 printf("null? %s,%lu,%i\n",
 ((struct s2 *)p)->string, ((struct s2 *)p)->len, ((struct s2 *)p)->setnull);
 p = string_analyse((void *)&s__);
 printf("null? %s,%lu,%i\n",
 ((struct s2 *)p)->string, ((struct s2 *)p)->len, ((struct s2 *)p)->setnull);
 printf("Réalisé en toute sécurité...\n"
 "Même pour un petit vaisseau serial killer du monde d'en haut.\n");
 return 0;
Tout en permettant l'optimisation:
struct s1{
 size_t len;
 char *str;
} s1;
union s {
 struct s1 dbl[2];
 void *perform[5];
 struct s1 simple;
<br>
ce qui signifie que:
int main(void)
 const char *end[2] = {", ", "\n"};
 union s init = }, us;
 void *p;
 int s1s = sizeof(struct s1);
 memset((void *)&us, 0,40);
 memcpy((void *)&us,
 (void *)&init,
 2 * s1s);
 for (p = &us;
 ((union s *)p)->perform[0] != (void *)0;
 p += s1s)
 printf("%s%s",
 (char *)((union s *)p)->perform[1],
 end[
 !((char *)((union s *)p)->perform[2])]
 return 0;
Définitions de synonymes de types (typedef).
Le langage C offre un mécanisme assez pratique pour définir des synonymes de types. Il s'agit du mot-clé codice_34.
typedef un_type synonyme_du_type;
Contrairement aux langages à typage fort comme le C++, le C se base sur les types atomiques pour décider de la compatibilité entre deux types. Dit plus simplement, la définition de nouveaux types est plus un mécanisme d'alias qu'une réelle définition de type. Les deux types sont effectivement parfaitement interchangeables. À la limite on pourrait presque avoir les mêmes fonctionnalités en utilisant le préprocesseur C, bien qu'avec ce dernier vous aurez certainement beaucoup de mal à sortir de tous les pièges qui vous seront tendus.
Quelques exemples.
typedef unsigned char octet;
typedef double matrice4_4[4][4];
typedef struct ma_structure * ma_struct;
typedef void (*gestionnaire_t)( int );
/* Utilisation */
octet nombre = 255;
matrice4_4 identite = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
ma_struct pointeur = NULL;
gestionnaire_t fonction = NULL;
Ce mot clé est souvent utilisé conjointement avec la déclaration des structures, pour ne pas devoir écrire à chaque fois le mot clé codice_35. Elle permet aussi de grandement simplifier les prototypes de fonctions qui prennent des pointeurs sur des fonctions en argument, ou retournent de tels types. Il est conseillé dans de tels cas de définir un type synonyme, plutôt que de l'écrire in extenso dans la déclaration du prototype. Considérez les deux déclarations :
/* Déclaration confuse */
void (*fonction(int, void (*)(int)))(int);
/* Déclaration claire avec typedef */
typedef void (*handler_t)(int);
handler_t fonction(int, handler_t);
Les vétérans auront reconnu le prototype imbitable de l'appel système codice_36, qui permet de rediriger les signaux (interruption, alarme périodique, erreur de segmentation, division par zéro, ...).
Énumérations.
enum nom_enum { val1, val2, ..., valN };
Les symboles "val1", "val2", ..., "valN" pourront être utilisés littéralement dans la suite du programme. Ces symboles sont en fait remplacés par des entiers lors de la compilation. La numérotation commençant par défaut à 0, s'incrémentant à chaque déclaration. Dans l'exemple ci-dessus, "val1" vaudrait 0, "val2" 1 et "valN" N-1.
On peut changer à tout moment la valeur d'un symbole, en affectant au symbole, la valeur constante voulue (la numérotation recommençant à ce nouvel indice). Par exemple :
enum Booleen { Vrai = 1, Faux = 0 };
/* Pour l'utiliser */
enum Booleen variable = Faux;
Ce qui est assez pénible en fait, puisqu'il faut à chaque fois se souvenir que le type codice_37 est dérivé d'une énumération. Il est préférable de simplifier les déclarations, grâce à l'instruction "typedef" :
typedef enum { Faux, Vrai } Booleen;
/* Pour l'utiliser */
Booleen variable = Faux;
Type incomplet.
Pour garantir un certain degré d'encapsulation, il peut être intéressant de masquer le contenu d'un type complexe, pour éviter les usages trop « optimisés » de ce type. Pour cela, le langage C permet de déclarer un type sans indiquer explicitement son contenu.
struct ma_structure;
/* Plus loin dans le code */
struct ma_structure * nouvelle = alloue_objet();
Les différents champs de la structure n'étant pas connus, le compilateur ne saura donc pas combien de mémoire allouer. On ne peut donc utiliser les types incomplets qu'en tant que pointeur. C'est pourquoi, il est pratique d'utiliser l'instruction codice_34 pour alléger les écritures :
typedef struct ma_structure * ma_struct;
/* Plus loin dans le code */
ma_struct nouvelle = alloue_objet();
Cette construction est relativement simple à comprendre, et proche d'une conception objet. À noter que le nouveau type défini par l'instruction codice_34 peut très bien avoir le même nom que la structure. C'est juste pour éviter les ambiguités, qu'un nom différent a été choisi dans l'exemple.
Un autre cas de figure relativement classique, où les types incomplets sont très pratiques, ce sont les structures s'auto-référençant, comme les listes chainées, les arbres, etc.
struct liste
 struct liste * suivant;
 struct liste * precedant;
 void * element;
Ou de manière encore plus tordue, plusieurs types ayant des références croisées :
struct type_a;
struct type_b;
struct type_a
 struct type_a * champ1;
 struct type_b * champ2;
 int champ3;
struct type_b
 struct type_a * ref;
 void * element;