« Programmation C/Types avancés » : différence entre les versions

Contenu supprimé Contenu ajouté
DannyS712 (discussion | contributions)
m <source> -> <syntaxhighlight> (phab:T237267)
 
Ligne 1 :
<noinclude>{{Programmation C}}</noinclude>
== Structures ==
<sourcesyntaxhighlight lang="c">
struct ma_structure {
type1 champ1;
Ligne 8 :
typeN champN;
} var1, var2, ..., varM;
</syntaxhighlight>
</source>
 
Déclare une structure (ou enregistrement) ''ma_structure'' composé de N champs, ''champ1'' de type ''type1'', ''champ2'' de type ''type2'', etc. On déclare, par la même occasion, M variables de type ''struct ma_structure''.
Ligne 15 :
L'accès aux champs d'une structure se fait avec un point :
 
<sourcesyntaxhighlight lang="c">
struct complexe {
int reel;
Ligne 23 :
c.reel = 1;
c.imaginaire = 2;
</syntaxhighlight>
</source>
 
=== Initialisation ===
Ligne 29 :
<ul>
<li>En initialisant les champs un à un :
<sourcesyntaxhighlight lang="c">
struct t_mastruct {
char ch;
Ligne 39 :
variable.nb = 12345;
variable.pi = 3.141592;
</syntaxhighlight>
</source>
Cette façon est néanmoins pénible lorsqu'il y a beaucoup de champs.
</li>
<li>
À la déclaration de la variable :
<sourcesyntaxhighlight lang="c">
struct t_mastruct {
char ch;
Ligne 51 :
};
struct t_mastruct variable = { 'a', 12345, 3.141592 };
</syntaxhighlight>
</source>
Les valeurs des champs sont assignés dans l'ordre où ils sont déclarés. S'il manque des initialisations, les champs seront initialisés à 0. L'inconvénient, c'est qu'on doit connaitre l'ordre où sont déclarés les champs, ce qui peut être tout aussi pénible à retrouver, et peut causer des plantages lorsque la définition de la structure est modifiée.
</li>
<li>
Une nouveauté du C99 permet d'initialiser certains champs à la déclaration de la variable, en les nommant :
<sourcesyntaxhighlight lang="c">
struct t_mastruct {
char ch;
Ligne 63 :
};
struct t_mastruct variable = { .pi = 3.141592, .ch = 'a', .nb = 12345 };
</syntaxhighlight>
</source>
Les champs non initialisés auront des valeurs indéfinies. Il devient possible de ne pas respecter l'ordonnancement des champs.
</li>
Ligne 76 :
Considérez la structure suivante:
 
<sourcesyntaxhighlight lang="c">
struct ma_structure {
char champ1; /* 8 bits */
Ligne 82 :
char champ3; /* 8 bits */
};
</syntaxhighlight>
</source>
 
On pourrait penser que cette structure occupera 6 octets en mémoire, et pourtant, sur une bonne partie des compilateurs, on obtiendrait une taille plus proche des 12 octets.
Ligne 104 :
L'existence d'octets de bourrage ainsi que leur nombre sont non seulement dépendants de l'architecture, mais aussi du compilateur. Cela dit, il est toujours possible de connaître la « distance » (''offset'') d'un champ par rapport au début de la structure, et ce, de manière portable. Pour cela il existe une macro, déclarée dans l'entête <code><stddef.h></code>:
 
<sourcesyntaxhighlight lang="c">
size_t offsetof(type, champ);
</syntaxhighlight>
</source>
 
La valeur renvoyée est le nombre de <code>char</code> (i.e. d'octets la plupart du temps), entre le début de la structure et celui du champ. Le premier argument attendu est bel et bien le ''type'' de la structure et non une variable de ce type, ni un pointeur vers une variable de ce type. Pour s'en souvenir, il suffit de savoir que beaucoup d'implémentations d'<code>offsetof</code> utilisent une arithmétique de ce genre:
 
<sourcesyntaxhighlight lang="c">
size_t distance = (size_t) &((type *)NULL)->champ;
</syntaxhighlight>
</source>
 
Si <code>type</code> était un pointeur, il faudrait faire un déréférencement supplémentaire (ou éviter l'étoile dans la macro). À noter que, même si cette macro peut s'avérer contraignante (notamment lorsqu'on ne dispose que de type pointeur), il est quand même préférable de l'utiliser pour des raisons de portabilité.
Ligne 118 :
Voici un petit exemple d'utilisation de cette macro:
 
<sourcesyntaxhighlight lang="c">
#include <stddef.h>
#include <stdio.h>
Ligne 141 :
return 0;
}
</syntaxhighlight>
</source>
 
Sur une architecture 32 bits, vous obtiendrez très certainement la réponse:
Ligne 150 :
Mais comme rien n'est toujours totalement négatif, les conséquences du bourrage offre énormément de souplesse :
imaginons 2 structures:
<sourcesyntaxhighlight lang="c">
struct str {
char *string;
Ligne 158 :
size_t len;
};
</syntaxhighlight>
</source>
quelle que soit la structure utilisé, on peut avec l'adresse et la cast (struct str *) accéder à la chaine.
<sourcesyntaxhighlight lang="c">
int main (void)
{
Ligne 176 :
return 0;
}
</syntaxhighlight>
</source>
<references />
 
Ligne 182 :
Il est (bien entendu) possible de déclarer des variables de type pointeur vers structure :
 
<sourcesyntaxhighlight lang="c">
struct ma_struct * ma_variable;
</syntaxhighlight>
</source>
 
Comme pour tout pointeur, on doit allouer de la mémoire pour la variable avant de l'utiliser :
 
<sourcesyntaxhighlight lang="c">
ma_variable = malloc( sizeof(struct ma_struct) );
</syntaxhighlight>
</source>
 
L'accès aux champs peut se faire comme pour une variable de type structure « normale » :
 
<sourcesyntaxhighlight lang="c">
(* ma_variable).champ
</syntaxhighlight>
</source>
 
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 :
 
<sourcesyntaxhighlight lang="c">
ma_variable->champ
</syntaxhighlight>
</source>
 
== Unions ==
Une union et un enregistrement se déclarent de manière identique :
 
<sourcesyntaxhighlight lang="c">
union type_union
{
Ligne 218 :
/* Déclaration de variables */
union type_union var1, var2, /* ... */ varM;
</syntaxhighlight>
</source>
 
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.
Ligne 224 :
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&nbsp;:
 
<sourcesyntaxhighlight lang="c">
union _XEvent
{
Ligne 241 :
long pad[24];
};
</syntaxhighlight>
</source>
 
La déclaration a juste été un peu raccourcie pour rester lisible. Les types <code>X*Event</code> de l'union sont en fait des structures contenant des champs spécifiques au message. Par exemple, si <code>type</code> vaut <code>ButtonPress</code>, seules les valeurs du champ <code>xbutton</code> seront significatives. Parmi ces champs, il y a <code>button</code> qui permet de savoir quel bouton de la souris à été pressé :
 
<sourcesyntaxhighlight lang="c">
XEvent ev;
XNextEvent(display, &ev);
Ligne 256 :
printf("Message type %d\n", ev.type);
}
</syntaxhighlight>
</source>
 
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!!!"):
 
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
#include <string.h>
Ligne 319 :
return 0;
}
</syntaxhighlight>
</source>
 
Tout en permettant l'optimisation:
 
<sourcesyntaxhighlight lang="c">
struct s1{
size_t len;
Ligne 334 :
struct s1 simple;
};
</syntaxhighlight>
</source>
<br>
<table border=1 cellspacing=0 cellpadding=5>
Ligne 367 :
ce qui signifie que:
 
<sourcesyntaxhighlight lang="c">
int main(void)
{
Ligne 388 :
return 0;
}
</syntaxhighlight>
</source>
 
== Définitions de synonymes de types (typedef) ==
Ligne 394 :
Le langage C offre un mécanisme assez pratique pour définir des synonymes de types. Il s'agit du mot-clé <code>typedef</code>.
 
<sourcesyntaxhighlight lang="c">
typedef un_type synonyme_du_type;
</syntaxhighlight>
</source>
 
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 ===
<sourcesyntaxhighlight lang="c">
typedef unsigned char octet;
typedef double matrice4_4[4][4];
Ligne 412 :
ma_struct pointeur = NULL;
gestionnaire_t fonction = NULL;
</syntaxhighlight>
</source>
 
Ce mot clé est souvent utilisé conjointement avec la déclaration des structures, pour ne pas devoir écrire à chaque fois le mot clé <code>struct</code>. 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 :
 
<sourcesyntaxhighlight lang="c">
/* Déclaration confuse */
void (*fonction(int, void (*)(int)))(int);
Ligne 424 :
 
handler_t fonction(int, handler_t);
</syntaxhighlight>
</source>
 
Les vétérans auront reconnu le prototype imbitable de l'appel système <code>signal()</code>, qui permet de rediriger les signaux (interruption, alarme périodique, erreur de segmentation, division par zéro, ...).
 
== Énumérations ==
<sourcesyntaxhighlight lang="c">
enum nom_enum { val1, val2, ..., valN };
</syntaxhighlight>
</source>
 
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.
Ligne 437 :
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 :
 
<sourcesyntaxhighlight lang="c">
enum Booleen { Vrai = 1, Faux = 0 };
 
/* Pour l'utiliser */
enum Booleen variable = Faux;
</syntaxhighlight>
</source>
 
Ce qui est assez pénible en fait, puisqu'il faut à chaque fois se souvenir que le type <code>Booleen</code> est dérivé d'une énumération. Il est préférable de simplifier les déclarations, grâce à l'instruction ''typedef'' :
 
<sourcesyntaxhighlight lang="c">
typedef enum { Faux, Vrai } Booleen;
 
/* Pour l'utiliser */
Booleen variable = Faux;
</syntaxhighlight>
</source>
 
== Type incomplet ==
Ligne 457 :
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.
 
<sourcesyntaxhighlight lang="c">
struct ma_structure;
 
/* Plus loin dans le code */
struct ma_structure * nouvelle = alloue_objet();
</syntaxhighlight>
</source>
 
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 <code>typedef</code> pour alléger les écritures :
 
<sourcesyntaxhighlight lang="c">
typedef struct ma_structure * ma_struct;
 
/* Plus loin dans le code */
ma_struct nouvelle = alloue_objet();
</syntaxhighlight>
</source>
 
Cette construction est relativement simple à comprendre, et proche d'une conception objet. À noter que le nouveau type défini par l'instruction <code>typedef</code> 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.
Ligne 477 :
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.
 
<sourcesyntaxhighlight lang="c">
struct liste
{
Ligne 484 :
void * element;
};
</syntaxhighlight>
</source>
 
Ou de manière encore plus tordue, plusieurs types ayant des références croisées :
 
<sourcesyntaxhighlight lang="c">
struct type_a;
struct type_b;
Ligne 504 :
void * element;
};
</syntaxhighlight>
</source>
 
<noinclude>