|
L'archive zippée tec001.zip contient les fichiers de la page.
PrincipeInventé dans les années 1970 par Brian W. Kernighan et Dennis M. Ritchie pour fabriquer le système d'exploitation (ou OS) Unix, ce langage n'a cessé de se diversifier pour couvrir finalement tous les secteurs imaginables du développement informatique. De nos jours la gratuité des environnements de développement (gcc, gdb, make, etc.) ainsi que le boum de Linux et du logiciel libre ne cessent de redonner une énième jeunesse à ce langage étonnant.
Le C est concis, multi-plateforme par excellence, le code exécutable est compact et rapide, son jeu d'instructions de base est réduit, mais la programmation spécifique de Windows ajoute une quantité incroyable d'éléments de l'API Win32. Pour des raisons historiques Apple, pour la programmation Carbon sur Macintosh, fonctionne de manière très proche avec là aussi une bibliothèque énorme de fonctionnalités gérant l'API.
Les pages de cette section présentent les principaux aspects du langage, mais pas tous et pas forcément dans l'ordre habituel - le livre de Kernighan et Ritchie comblera les blancs pour les lecteurs qui le désirent. Dans cette page nous allons voir des notions un peu pointues du C, histoire d'être à l'aise avec le reste, les API Win32.
Tous les programmes de ce site contiennent au moins une fonction
main si c'est une application console - Invite de Commandes - (ou
WinMain si c'est une application avec API Windows, ou encore
ServiceMain si c'est un service).
Cette fonction est le point d'entrée du programme, le minimum à compiler:
#include <stdio.h>
int main(int argc, char *argv[]) {
// Cette fonction affiche Bonjour programmeur dans le flux stdout, ici l'écran:
fprintf(stdout, "Bonjour programmeur");
return(0);
}
PointeursUn pointeur est une variable qui contient l'adresse en mémoire vive d'une autre
variable, et quand nous avons l'adresse d'une variable nous pouvons lire et écrire sur
cette variable.
C'est assez simple à dire mais un peu plus délicat à implémenter. Pourquoi ?
D'abord parce que la syntaxe est un peu déroutante, ensuite parce que le maniement
des pointeurs demande de la précision. Tant que nous sommes dans la zone autorisée
tout va bien, mais dès que nous débordons, il arrive que le système d'exploitation
attrape notre programme par l'oreille et nous le ramène, en disant qu'il a essayé de
violer un segment de mémoire (signal SIGSEGV).
Dans le code d'un programme avec API Win32, de nombreuses fonctions prennent pour paramètres des pointeurs de structures (polices de caractères, messages complexes, etc.) - donc voici les principes du pointeur.
Déclarons un entier i initialisé à 3, un pointeur d'entier ptri et affectons l'adresse de i à ptri - autrement dit, faisons pointer ptri sur i:
int i = 3; int *ptri; ptri = &i;
La syntaxe du C nous donne la clé: int *ptri s'écrit aussi
int * ptri ou encore int* ptri:
donc ptri est un int* et *ptri est un int.
Dit en français, ptri est un pointeur d'entier, et une fois déréférencé par l'opérateur
* c'est un entier - donc *ptri est bien un entier et peut être utilisé comme tel.
D'ailleurs, nous pouvons lire et modifier la valeur de i en nous servant de ptri,
comme ceci:
printf("*ptri=%d, i=%d", *ptri, i); // Même résultat
*ptri = 5;
Voici le parcours classique d'un pointeur, illustré par le code source tec001b.c dont
la copie d'écran figure ci-contre: il est déclaré
par int *piPointeur, nous allouons de la mémoire dans le tas (heap)
par la fonction malloc en vérifiant que le pointeur alloué n'est pas NULL,
nous écrivons et lisons u moyen de ce pointeur, puis nous libérons la mémoire par la
fonction free (source tec001b.c).
L'expression NULL dont il est question est l'adresse zéro (0x0).
Pas l'entier zéro, l'adresse zéro en mémoire vive qui,
par convention (selon la norme ANSI), n'est pas occupée. Un pointeur à NULL n'est donc
jamais utilisable, et de nombreuses fonctions renvoient NULL en cas de problème
justement pour permettre au développeur sérieux de toujours vérifier le caractère
non-NULL d'un pointeur avant de s'en servir. Par exemple pour l'API Win32, la fonction
CreateWindow renvoie un handle de contrôle, que nous devons tester à NULL
avant de l'utiliser, sous peine de nous demander pourquoi il ne s'affiche pas
à l'écran.
Pour un problème de pointeur provoqué, détecté par le débogueur, corrigé et surveillé par un breakpoint, voir dans l'onglet Outils à propos du débogueur (gdb).
StructuresUne structure est une variable composée d'éléments souvent hétérogènes, toujours les mêmes pour une structure donnée, qu'on appelle des membres - ce peuvent être des variables ou des fonctions - et que la structure permet de rassembler au lieu d'avoir à les gérer séparément. D'autres langages utilisent un record (Pascal), un type (Basic), une classe (C++, Java) mais le principe est le même. Par équivalence, une structure de langage C est comme une classe C++ dont tous les membres sont publics et dont les caractéristiques objet (héritage, polymorphisme, encapsulation) sont possibles bien que peu pratiques à implémenter. On peut déclarer une structure, un pointeur sur une structure, un tableau de structures, la passer en paramètre par copie ou par pointeur, s'en servir comme valeur de retour d'une fonction, l'affecter par une liste de valeurs constantes à condition de les présenter dans l'ordre et avec le bon type; par contre il n'est pas possible de comparer deux structures.
Pour être rigoureux, il nous faut gérer les membres d'une structure un à un.
L'accès aux membres d'une structure se fait par l'opérateur point ou par
l'opérateur flèche composé d'un signe moins et d'un supérieur (->). Ce dernier est
pratique lorsqu'on se sert d'un pointeur de structure. Voyons le code du fichier
d'en-tête qui déclare le contenu d'une structure, ici appelé CHAINE et déclarée en
typedef ou définition de type, ce qui simplifie un peu le code pour
la suite:
typedef struct tag_CHAINE {
char *s;
long l;
} CHAINE;
La structure CHAINE possède deux membres, un entier long qui est la longueur de
la chaîne et un pointeur de caractères. Nous accédons aux membres par l'opérateur point
chn.l et chn.s, qui est d'ailleurs équivalent à
(&chn)->s car la notation flèche sert pour un pointeur de structure, et
on sait que &chn est l'adresse de chn donc un pointeur vers
chn.
Nous déclarons ensuite deux fonctions, qui sont le minimum vital pour utiliser la
structure CHAINE:
void ViderChaine(CHAINE *pchn); long EcrireChaine(CHAINE *pchn, char *sEntree);
La fonction ViderChaine prend comme paramètre un pointeur de structure
CHAINE et ne renvoie pas de valeur (type void), la fonction EcrireChaine
prend comme paramètres un pointeur de CHAINE et un pointeur de caractères et renvoie
soit un code d'erreur, soit la longueur de la chaîne recopiée dans la structure CHAINE.
En pratique, la première libère la mémoire allouée au sein de CHAINE et la
deuxième sert à recopier sEntree dans le membre s de la structure CHAINE.
Si nous déclarons un pointeur de structure CHAINE *pchn,
alors l'accès au pointeur de caractères s, un des deux membres de la structure,
se fait par pchn->s ou (*pchn).s.
De même si pchn pointe sur un tableau de CHAINE, alors l'accès à s par exemple dans le
troisième élément se fait
par l'une de ces expressions, qui sont équivalentes: (&pchn[2])->s,
(pchn + 2)->s ou pchn[2].s.
Si, en plus d'un pointeur CHAINE *pchn = &chn[0], nous déclarons un
tableau de structures CHAINE chn[3], il faut se
rappeler que le nom d'un tableau est un pointeur constant vers le premier
élément: chn est équivalent à &chn[0] et à
pchn.
Ce qui précède peut prêter à confusion, mais le plus simple est de tester le
code et de se servir des expressions les plus utiles - voir le code source de
tec001.c. De toutes façons ne vous en faites pas, le compilateur va couiner à
chaque fois que vous ne lui donnerez pas exactement ce qu'il attend - veillez
quand même à compiler avec l'option -Wall ou au minimum
-Wformat si vous voulez qu'il vérifie ce que vous balancez dans les
printf en termes de format.
Personnellement j'emploie le plus souvent des pointeurs vers les
structures, car comme ce sont des fonctions qui font le travail spécifique de
créer, lire, écrire, copier, swapper, trier des structures, je leur passe un pointeur
vers la structure ou vers le premier élément d'un tableau de structures. Du coup, les
notations pchn->s, (pchn + i)->s ou
pchn[i].s sont les plus naturelles à utiliser.
Fabrication d'un programme
Le petit programme qui suit illustre ce qui a été vu plus haut, à savoir
la structure CHAINE. Il est constitué d'un fichier
source (tec001.c) et d'un fichier d'en-tête (tec001.h).
Une instruction de préprocesseur #include "tec001.h" au début de
tec001.c permet d'utiliser le contenu du fichier d'en-tête.
Pour exécuter le programme fabriqué à partir du fichier source tec001.c qui résume
les éléments de cette page, nous pouvons le compiler à l'aide de gcc en
deux étapes, en nous plaçant dans le répertoire contenant le fichier
source (1):
Génération du code objet tec001.o, instruction suffisante:(2)
gcc -c tec001.c
Fabrication de l'exécutable tec001.exe:
gcc -o tec001.exe tec001.o
Toujours sous "Invite de commandes" il faudra taper (sans les guillemets) "tec001 truc chose" ou d'autres chaînes de caractères, toujours précédées du nom de l'exécutable (tec001). La copie d'écran nous montre à quoi ça peut ressembler.
(1) Oui, le terminal ou le DOS ou l'Invite de commandes restent d'actualité - demandez à quelqu'un qui travaille sur machine Unix ce qu'il (elle) pense de la puissance des commandes en ligne - et au XXIème siècle il est toujours utile de connaître dans Microsoft Windows:
cd \ pour "remonter à la racine du volume";cd .. pour "remonter d'un niveau de répertoire";cd truc pour "descendre dans le répertoire truc";dir /p /o-d pour "lister le contenu du répertoire en cours,
page par page, en ordre chronologique descendant".Par curiosité, essayez aussi ver, ipconfig ou
path.
(2) La même chose, version alternative avec les options pour que les caractères accentués iso-8859-1 du fichier source apparaissent correctement à l'écran dont le jeu de caractères est la page de code 850:
gcc -c -fexec-charset=cp850 -finput-charset=iso-8859-1 tec001.c
[màj 22 avril 2008]