# Programmation C/Pointeurs

* Programmation C
* Sommaire
* Introduction
* Bases du langage
* Types de base
* Classe de stockage
* Opérateurs
* Tests
* Itérations
* Fonctions
* Tableaux
* Pointeurs
* Types avancés
* Préprocesseur
* Bibliothèque
* Chaînes
* Entrées/sorties
* Erreurs
* Mathématiques
* Courbe de Bézier
* Triangle de Sierpiński
* Gestion de la mémoire
* Gestion des signaux
* Conclusion
* Bibliographie
* Modifier ce modèle
Dans cette section, nous allons présenter un mécanisme permettant de manipuler les adresses, les pointeurs. Un pointeur a pour valeur l'adresse d'un objet C d'un type donné (un pointeur est typé). Ainsi, un pointeur contenant l'adresse d'un entier sera de type pointeur vers entier.

## Usage et déclaration

L'opérateur & permet de connaitre l'adresse d'une variable, on dira aussi la référence. Toute déclaration de variable occupe un certain espace dans la mémoire de l'ordinateur. La référence permet de savoir où cet emplacement se trouve. En simplifiant à l'extrême, on peut considérer la mémoire d'un ordinateur comme une gigantesque table d'octets. Quand on déclare une variable de type int, elle sera allouée à un certain emplacement (ou dit autrement : un indice, une adresse ou une référence) dans cette table. Un pointeur permet simplement de stocker une référence, il peut donc être vu comme un nombre allant de 0 à la quantité maximale de mémoire dont dispose votre ordinateur (moins un, pour être exact).
Un pointeur occupera habituellement toujours la même taille (occupera la même place en mémoire), quel que soit l'objet se trouvant à cet emplacement. Il s'agit en général de la plus grande taille directement gérable par le processeur : sur une architecture 32bits, elle sera de 4 octets, sur une architecture 64bits, 8 octets, etc. Le type du pointeur ne sert qu'à renseigner comment sont organisées les données suivant l'adresse référencée par le pointeur. Ce code, par exemple, affiche la référence d'une variable au format hexadécimal :
Pouvoir récupérer l'adresse n'a d'intérêt que si on peut manipuler l'objet pointé. Pour cela, il est nécessaire de pouvoir déclarer des pointeurs, ou dit autrement un objet pouvant contenir des références. Pour cela on utilise l'étoile (*) entre le type et le nom de la variable pour indiquer qu'il s'agit d'un pointeur :
Déclare les variables pointeur, pointeur2, ..., pointeurN de type pointeur vers le type T. À noter la bizarrerie du langage à vouloir associer l'étoile à la variable et non au type, qui oblige à répéter l'étoile pour chaque variable.
Cet exemple de code déclare un pointeur sur un entier de type int et une variable de type int. Dans un vrai programme, il est rarement possible d'utiliser des noms aussi triviaux, aussi il est recommandé de séparer la déclaration des variables de celles des pointeurs (ou d'utiliser l'instruction typedef, qui, elle, permet d'associer l'étoile au type), la lisibilité du programme sera légèrement améliorée.
Il est essentiel de bien comprendre ce qui a été déclaré dans ces exemples. Chaque pointeur peut contenir une référence sur un emplacement de la mémoire (un indice dans notre fameuse table). On peut obtenir une référence (ou un indice) avec l'opérateur & (ou allouer une référence soi-même avec des fonctions dédiées, c.f la section suivante). Cet opérateur transforme donc une variable de type T en un pointeur de type T *. Insistons sur le terme variable, car évidemment des expressions telles que '&23435' ou '&(2 * a / 3.)' n'ont aucun sens, dans la mesure où les constantes et expressions du langage n'occupent aucun emplacement susceptible d'intéresser votre programme.
Ce code, par exemple, affiche la référence d'une variable dans un format défini par l'implémentation (qui peut être hexadécimal, ou une combinaison "segment:offset", par exemple) :
Il ne faut pas oublier que, comme toutes les variables locales en C, un pointeur est à l'origine non initialisé. Une bonne attitude de programmation est de s'assurer que lorsqu'il ne pointe pas vers un objet valide, sa valeur est mise à zéro (ou NULL, qui est déclaré entre autre dans <stdio.h>).

## L'arithmétique des pointeurs

L'arithmétique associée aux pointeurs est sans doute ce qui a valu au C sa réputation « d'assembleur plus compliqué et plus lent que l'assembleur ». On peut très vite construire des expressions incompréhensibles avec les opérateurs disponibles. Dans la mesure du possible, il est conseillé de se limiter à des expressions simples, quitte à les décomposer, car la plupart des compilateurs savent très bien optimiser un code C.

### Déréférencement

Le déréférencement ou indirection est l'opération la plus simple sur les pointeurs. Comme son nom l'indique, il s'agit de l'opération réciproque au référencement (&). L'opérateur associé est l'étoile (*), qui est aussi utilisé pour déclarer un type pointeur. Cet opérateur permet donc de transformer un pointeur de type T *, en un objet de type T, les opérations affectant l'objet pointé :
Ici, pointeur contient une adresse valide, celle de variable ; son déréférencement est donc possible. Par contre, si pointeur était une variable locale non initialisée, son déréférencement provoquerait à coup sûr un arrêt brutal de votre programme.
Vous obtiendrez le même résultat, si pointeur est initialisé à NULL. Cette adresse est invalide et toute tentative de déréférencement se soldera par un arrêt du programme.

### Arithmétique de base

L'arithmétique des pointeurs s'apparente à celle des entiers, mais il est important de comprendre la distinction entre ces deux concepts.
Les opérations arithmétiques permises avec les pointeurs sont :
Addition / soustraction d'une valeur entière à un pointeur (on avance / recule d'un nombre de cases mémoires égal à la taille du type T) : le résultat est donc un pointeur, de même type que le pointeur de départ.
Il faut bien faire attention avec ce genre d'opération à ne pas sortir du bloc mémoire, car le C n'effectuera aucun test pour vous. Considérez l'exemple suivant :
Normalement un tableau de N cases permet d'être itéré sur les indices allant de 0 à N - 1, inclusivement. L'expression &tableau[N] fait référence la case mémoire non allouée immédiatement après le plus grand indice, donc potentiellement source de problème. Toutefois, par exception pour le premier indice après le plus grand, C garantit que le résultat de l'expression soit bien défini. Bien sûr, il ne faut pas déréférencer ce pointeur.
À noter qu'à l'issue de la boucle, p pointera sur la N+1ème case du tableau, donc hors de l'espace alloué. Le C autorise tout à fait ce genre de pratique, il faut juste faire attention à ne pas déréférencer le pointeur à cet endroit.
Soustraction de deux pointeurs de même type (combien d'objet de type T y a t-il entre les deux pointeurs) : le résultat est donc un entier, de type ptrdiff_t.
Dans cet exemple, les deux premières soustractions sont définies, car p et q pointent sur des éléments du même tableau. La troisième soustraction est indéfinie, car on utilise des adresses d'éléments de tableaux différents.
Notons que l'opérateur s'applique toujours à une opérande de type entier et une autre de type pointeur. Lorsqu'on écrit tableau[i], il y a en fait une conversion de tableau à pointeur avec l'application de l'opérateur. On peut donc bien sûr utiliser l'opérateur avec un pointeur pour opérande :

### Arithmétique avec effet de bord

C'est sans doute ce qui a donné des sueurs froides à des générations de programmeurs découvrant le C : un usage « optimisé » de la priorité des opérateurs, le tout imbriqué dans des expressions à rallonge. Par exemple 'while( *d++ = *s++ );', pour copier une chaine de caractères.
En fait, en décomposant l'instruction, c'est nettement plus simple qu'il ne parait. Par exemple :
Dans ce cas de figure, l'opérateur d'incrémentation ayant priorité sur celui de déréférencement, c'est celui-ci qui sera appliqué en premier. Comme il est postfixé, l'opérateur ne prendra effet qu'à la fin de l'expression (donc de l'affectation). La variable i sera donc tout simplement affectée de la valeur pointée par entier et après cela le pointeur sera incrémenté. Voici les différents effets suivant les combinaisons de ces deux opérateurs :
On peut évidemment complexifier les expressions à outrance, mais privilégier la compacité au détriment de la clarté et de la simplicité dans un hypothétique espoir d'optimisation est une erreur de débutant à éviter.

## Le pointeur void *

Ce pointeur est un cas particulier. Il permet de pointer sur un type quelconque. Il est notamment utilisé dans la fonction malloc:
En pratique, il faut penser à transformer ce pointeur pour qu'il devienne utilisable, même si certains compilateurs acceptent de l'utiliser directement:

## Tableaux dynamiques

Un des intérêts des pointeurs et de l'allocation dynamique est de permettre de décider de la taille d'une variable au moment de l'exécution, comme par exemple pour les tableaux. Ainsi pour allouer un tableau de n entiers (n étant connu à l'exécution), on déclare une variable de type pointeur sur entier à laquelle on alloue une zone mémoire correspondant à n entiers :
Cet exemple alloue un tableau de 256 cases. Bien que la variable soit un pointeur, il est dans ce cas permis d'accéder aux cases de 0 à 255, soit entre les adresses &tableau[0] et &tableau[255], incluses.

### Tableaux dynamiques à deux dimensions

Tout comme on pouvait allouer des tableaux statiques à plusieurs dimensions, on peut allouer des tableaux dynamiques à plusieurs dimensions. Pour ce faire, on commence là-aussi par déclarer un pointeurs approprié : un pointeur sur des pointeurs (etc.) sur des types. Pour déclarer un tableau dynamique d'entiers à deux dimensions :
L'allocation d'un tel objet va se dérouler en plusieurs étapes (une par étoile), on alloue d'abord l'espace pour un tableau de pointeurs vers entier. Ensuite, on alloue pour chacun de ces tableaux l'espace pour un tableau d'entiers. Si on veut une matrice 4x5 :
Il ne faut jamais oublier de libérer la mémoire allouée précédemment. Ainsi, à tout appel de malloc doit correspondre un appel de free Pour libérer l'espace alloué ci-dessus, on procède de manière inverse, en commençant par libérer chacune des lignes du tableau, puis le tableau lui même :

### Tableaux dynamiques à trois dimensions

Voici maintenant un exemple d'une fonction créant un tableau à trois dimensions avec surtout, et c'est très important, les tests des valeurs de retour des fonctions malloc :

## Utilisation des pointeurs sur des tableaux particuliers

Il est possible avec un pointeur de lire/parcourir les éléments d'une structure. Chaque éléments d'une structure utilise un espace qui permet de calculer des déplacements.
Cependant cette utilisation est dangereuse, comme dans l'exemple ci-dessous qui ne fonctionne que sur les processeurs où la taille d'un pointeur égale celle d'un entier de type int, ce qui n'est généralement pas le cas sur les processeurs 64 bits.

## Utilisation des pointeurs pour passer des paramètres par adresse

Toutes les variables en C, à l'exception des tableaux, sont passés par valeurs aux paramètres des fonctions. C'est à dire qu'une copie est effectuée sur la pile d'appel. Si bien que toutes les modifications de la variable effectuées dans la fonction seront perdues une fois de retour à l'appelant. Or, il y a des cas où l'on aimerait bien pouvoir modifier une variable passée en paramètre et que ces modifications perdurent dans la fonction appelante. C'est un des usages des paramètres par adresse : permettre la modification d'une variable de l'appelant, comme dans l'exemple suivant :
Ce passage par adresse est extrêmement répandu pour optimiser la quantité de données qui doit transiter sur la pile d'appel (qui est, sur beaucoup de systèmes, de taille fixe). En fait, même si la variable ne doit pas être modifiée, on utilise quand même un passage par adresse, juste pour éviter la copie implicite des variables autres que les tableaux. Ceci est particulièrement intéressant avec les structures, puisque celles-ci ont tendance à être assez imposantes, et cela ne nuit pas trop la lisibilité du programme.

## Tableaux dynamiques et passage de tableaux comme arguments d'une fonction

Comme on vient de le voir, l'intérêt principal d'une allocation dynamique est de pouvoir lancer son programme sans connaître la taille du tableau qu'on utilisera; celle-ci sera établie en cours de fonctionnement. Cependant, un autre intérêt du tableau dynamique est de pouvoir être passé comme argument d'une fonction pour des tableaux à plusieurs dimensions.
En reprenant la fonction malloc_3d vue précédemment, on peut écrire :

## Pointeurs vers fonctions

Les pointeurs vers les fonctions sont un peu spéciaux, parce qu'ils n'ont pas d'arithmétique associée (car une telle arithmétique n'aurait pas beaucoup de sens). Les opérations permises avec les pointeurs sur fonctions sont en fait relativement limitées :
Déclare pointeur_fonction, un pointeur vers une fonction prenant liste_paramètres comme paramètres et renvoyant type_retour. Le parenthésage est ici obligatoire, sans quoi l'étoile se rattacherait au type de retour. Pour faire pointer un pointeur vers une fonction, on utilise une affectation « normale » :
Où fonction est compatible avec le pointeur (mêmes paramètres et valeur de retour). Une fois que le pointeur pointe vers une fonction, on peut appeler cette fonction :

### Exemple simple d'utilisation de pointeur de fonction avec retour

On fait une comparaison de deux entiers (5 et 4) via un pointeur sur une fonction de comparaison.

## Pointeurs de fonctions

* Exemple sans pointeur
* On écrit une fonction graphique.
* Cette fonction dessine f.
* //INITIALISATION :
* f est une FORME GRAPHIQUE
* f := "_/-\_"
* //DECLARATION DE LA FONCTION dessine_f :
* fonction dessine_f
* dessine f
* fin de fonction
* Cela donne le résultat :
* _/-\_
* Avec la fonction f qui est fixe, dessine_f ne sait dessiner que la forme "_/-\_".
* Si on remplace la valeur intrinsèque de f par un pointeur de fonction, la fonction pourra dessiner n'importe quelle forme.
* Même exemple en utilisant un pointeur de fonction
* En C, le nom d'une fonction est un pointeur. On peut l'utiliser comme argument :
* //INITIALISATION :
* g, h sont des FORMES GRAPHIQUES
* g := "/-\/-\"
* h := "--\/--"
* //DECLARATION DE LA FONCTION dessine_f :
* fonction dessine_f(P_f)
* dessine P_f
* fin de fonction
* dessine_f(g) donnera :"/-\/-\"
* dessine_f(h) donnera :"--\/--"
* Voir Exemple graphique (avec Gnuplot) ci-dessous.

### Exemple numérique

* Testé sous Code Block (Windows,Linux).
* Passer deux pointeurs de fonctions à une fonction.

#### Code source

* Ici on passe les deux fonctions f et g à la fonction f1_o_f2.
* La même fonction peut calculer gof, fog et fof...
* On peut remarquer que les pointeurs de fonctions ont les mêmes types arguments que les fonctions qu'ils vont recevoir.
Résultat ;

### Exemple graphique (avec Gnuplot)

* Testé sous Code Block (Windows,Linux).
* Passer un pointeurs de fonctions à une fonction.
* Vous pourrez approfondir ce travail en analyse avec le cours de Wikiversité: Mathc Home Edition.

#### Code source

* La fonction Gplt dessine f(x) et g(x)...
* On peut remarquer que les pointeurs de fonctions ont les mêmes types arguments que les fonctions qu'ils vont recevoir.

## Tableau de pointeurs de fonctions
### Premier exemple

Nous avons des fonctions semblables. Nous voulons les associer pour pouvoir les manipuler dans des boucles.
Nous allons créer un tableau de pointeurs de fonctions.
Je vais utiliser les fonctions trigonométriques prédéfinies pour faciliter la lecture.

#### Déclaration d'un tableau de pointeurs de fonctions

* Toutes les fonctions ont la même forme.
* double FUNCTION(double)
* Le tableau à la même forme que les fonctions.
* double ARRAY(double)
* Il y a six fonctions. (0,1,2,3,4,5)= {cos,sin,tan,atan,asin,acos}.

#### Application

* Créer un tableau de valeurs des fonctions trigonométriques.
* Imprimer le résultat dans cette ordre (sin,cos,tan,acos,asin,atan).
* Pour .1 <= x <= .5
* Ce travail pourrait être récupéré dans une matrice.

#### Résultat dans un fichier

Le résultat :
Remarque :
* Attention à l'ordre des fonctions dans la déclaration du tableau.
* double (*TrigF[6])(double x) = {atan,asin,acos,tan,cos,sin};
Au démarrage :
* La décrémentation se fait dans le tableau. TrigF[--i](x)
* Il entre 6 dans le tableau.
* 6 est décrémenté -> 5 (avant l'appel de la fonction --i)
* La sixième fonction est appelée (Sin).
* La numéro est cinq. :)
Au final :
* Il entre 1 dans le tableau.
* 1 est décrémenté -> 0
* La première fonction est appelée (atan).
* La numéro zéro. :)
* i est égal à zéro en rentrant dans la boucle.
* Le cycle est cassé. :(

#### Avec résultat à l'écran
### Deuxième exemple

Nous voulons créer la fonction Derivate capable de calculer la dérivé première et seconde d'une fonction, en utilisant un tableau de pointeurs de fonctions.

#### Déclaration d'un tableau de pointeurs de fonctions

* Voir listing en fin de page.
* Toutes les fonctions (fx,Dx_1,Dx_2) ont la même forme.
* double FUNCTION(double (*P_f)(double x) double double)
* Le tableau à la même forme que les fonctions.
* double ARRAY(double (*P_f)(double x) double double)
* Il y a trois fonctions. (0,1,2)= {fx, Dx_1, Dx_2}.
* La fonction fx donne f.
* Supprimer cette fonction et travailler sur deux fonctions.
* Réfléchissez.

#### Exemple d'un appel

* Derivate[0] donne f(x).
* Voir la fonction fx la première fonction du tableau.
* h = 0 dans cet appel parce qu'il n'est pas utilisé (voir code de fx)