« Programmation C/Pointeurs » : différence entre les versions

Contenu supprimé Contenu ajouté
→‎Déréférencement : syn: indirection
DannyS712 (discussion | contributions)
m <source> -> <syntaxhighlight> (phab:T237267)
Ligne 7 :
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 :
 
<sourcesyntaxhighlight lang="c">int i;
 
printf("%p\n", &i);
</syntaxhighlight>
</source>
 
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 (<code>*</code>) entre le type et le nom de la variable pour indiquer qu'il s'agit d'un pointeur :
 
<sourcesyntaxhighlight lang="c">
T * pointeur, * pointeur2, /* ..., */ * pointeurN;
</syntaxhighlight>
</source>
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.
 
<sourcesyntaxhighlight lang="c">
/* Ce code contient une déclaration volontairement confuse */
int * pointeur, variable;
</syntaxhighlight>
</source>
 
Cet exemple de code déclare un ''pointeur sur un entier de type <code>int</code>'' et une variable de type ''<code>int</code>''. 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 <code>[[Programmation C Types avancés#Définitions de nouveaux types (typedef)|typedef]]</code>, qui, elle, permet d'associer l'étoile au type), la lisibilité du programme sera légèrement améliorée.
Ligne 29 :
 
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) :
<sourcesyntaxhighlight lang="c">
int i;
 
printf("%p\n", &i);
</syntaxhighlight>
</source>
 
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 <code>NULL</code>, qui est déclaré entre autre dans <code><stdio.h></code>).
Ligne 44 :
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 (<code>&amp;</code>). L'opérateur associé est l'étoile (<code>*</code>), 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é :
 
<sourcesyntaxhighlight lang="c" line="1">
int variable = 10;
int * pointeur = &variable;
 
*pointeur = 10; /* la valeur de variable est = à 10 */
</syntaxhighlight>
</source>
Ici, <code>pointeur</code> contient une adresse valide, celle de <code>variable</code> ; son déréférencement est donc possible. Par contre, si <code>pointeur</code> était une variable locale non initialisée, son déréférencement provoquerait à coup sûr un arrêt brutal de votre programme.
 
Ligne 63 :
 
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 :
<sourcesyntaxhighlight lang="c">/* Parcours les éléments d'un tableau */
int tableau[N];
int * p;
Ligne 70 :
{
/* ... */
}</sourcesyntaxhighlight>
 
Normalement un tableau de N cases permet d'être itéré sur les indices allant de 0 à N - 1, inclusivement. L'expression <code>&tableau[N]</code> 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.
Ligne 80 :
 
{{Erreur volontaire}}
<sourcesyntaxhighlight lang="c">int autre_tableau[3];
int tableau[10];
int * p = &tableau[5]; /* p pointe sur le 6e élément du tableau */
Ligne 89 :
q = &autre_tableau[2];
ptrdiff_t dif3 = p - q; /* Erreur ! */
</syntaxhighlight>
</source>
 
Dans cet exemple, les deux premières soustractions sont définies, car <code>p</code> et <code>q</code> 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 <code>[]</code> s'applique toujours à une opérande de type entier et une autre de type pointeur. Lorsqu'on écrit <code>tableau[i]</code>, il y a en fait une conversion de tableau à pointeur avec l'application de l'opérateur <code>[]</code>. On peut donc bien sûr utiliser l'opérateur <code>[]</code> avec un pointeur pour opérande :
<sourcesyntaxhighlight lang="c">int a;
int b;
int * p = &a; /* On peut accéder à la valeur de 'a' via 'p[0]' ou '*p' */
 
/* p[1] est indéfini - n'espérez pas accéder à la valeur de b depuis l'adresse de a */
</syntaxhighlight>
</source>
 
 
Ligne 106 :
 
En fait, en décomposant l'instruction, c'est nettement plus simple qu'il ne parait. Par exemple :
<sourcesyntaxhighlight lang="c">
int i;
int * entier;
Ligne 113 :
 
i = *entier++; /* i = *(entier++); */
</syntaxhighlight>
</source>
 
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 :
<sourcesyntaxhighlight lang="c">
i = *++entier; /* Incrémente d'abord le pointeur, puis déréférence la nouvelle adresse pointée */
i = ++*entier; /* Incrémente la valeur pointée par "entier", puis affecte le résultat à "i" */
i = (*entier)++; /* Affecte la valeur pointée par "entier" et incrémente cette valeur */
</syntaxhighlight>
</source>
 
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.
Ligne 126 :
== Le pointeur <code>void *</code> ==
Ce pointeur est un cas particulier. Il permet de pointer sur un type quelconque. Il est notamment utilisé dans la fonction malloc():
<sourcesyntaxhighlight lang = "c">
void * malloc(int n);
</syntaxhighlight>
</source>
En pratique, il faut penser à transformer ce pointeur pour qu'il devienne utilisable, même si certains compilateurs acceptent de l'utiliser directement:
<sourcesyntaxhighlight lang = "c">
// Allocation avec conversion
int * p; // Pointeur p sur le type int
Ligne 140 :
p = malloc(sizeof(int) * 10); // Allocation de 10 int, soit 20 octets
*p = 4; // Modification
</syntaxhighlight>
</source>
 
== 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 :
<sourcesyntaxhighlight lang="c">int * alloue_tableau(int n, size_t taille)
{
return malloc(n * taille);
Ligne 157 :
free( tableau );
}
</syntaxhighlight>
</source>
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 <code>&tableau[0]</code> et <code>&tableau[255]</code>, incluses.
 
Ligne 164 :
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 :
 
<sourcesyntaxhighlight lang="c">
int ** matrice;
</syntaxhighlight>
</source>
 
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 :
 
<sourcesyntaxhighlight lang="c">
#define LIGNES 4
#define COLONNES 5
Ligne 180 :
matrice[i] = malloc(sizeof **matrice * COLONNES);
}
</syntaxhighlight>
</source>
 
Il ne faut jamais oublier de libérer la mémoire allouée précédemment. Ainsi, à tout appel de <code>malloc</code> doit correspondre un appel de <code>free</code>
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 :
<sourcesyntaxhighlight lang="c">
for(i = 0; i < LIGNES; i++)
{
Ligne 190 :
}
free(matrice);
</syntaxhighlight>
</source>
 
=== 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 <code>malloc</code> :
<sourcesyntaxhighlight lang="c">
int *** malloc_3d(int nb_tableau, int lignes, int colonnes)
{
Ligne 228 :
return t;
}
</syntaxhighlight>
</source>
 
== Utilisation des pointeurs sur des tableaux particuliers ==
Ligne 234 :
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.
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
#include <string.h>
Ligne 271 :
return 0;
}
</syntaxhighlight>
</source>
 
Vous trouverez un exemple complet sur les tableaux et les pointeur ici[http://fr.wikibooks.org/wiki/Discussion:Programmation_C/Pointeurs#Exemple_Complet_sur_les_boucles.2C_les_tableaux_et_les_pointeurs]
Ligne 279 :
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 :
 
<sourcesyntaxhighlight lang="c">#include <stdio.h>
/* Ce code échange le contenu de deux variables */
void echange(int *a, int *b)
Ligne 311 :
return 0;
}
</syntaxhighlight>
</source>
 
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.
Ligne 320 :
 
En reprenant la fonction <code>malloc_3d()</code> vue précédemment, on peut écrire :
<sourcesyntaxhighlight lang="c">
int fonction_3d (int ***tab);
 
Ligne 342 :
*/
}
</syntaxhighlight>
</source>
 
== 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 :
 
<sourcesyntaxhighlight lang="c">
type_retour (*pointeur_fonction)(liste_paramètres);
</syntaxhighlight>
</source>
Déclare <code>pointeur_fonction</code>, un pointeur vers une fonction prenant <code>liste_paramètres</code> comme paramètres et renvoyant <code>type_retour</code>. 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 » :
<sourcesyntaxhighlight lang="c">
pointeur_fonction = &fonction;
/* Qui est en fait équivalent à : */
pointeur_fonction = fonction;
</syntaxhighlight>
</source>
Où <code>fonction</code> 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 :
<sourcesyntaxhighlight lang="c">
(*pointeur_fonction)(paramètres);
/* Ou plus simplement, mais moins logique syntaxiquement */
pointeur_fonction(paramètres);
</syntaxhighlight>
</source>
 
=== 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.
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
#include <stdlib.h>
Ligne 389 :
}
 
</syntaxhighlight>
</source>
 
== Pointeurs de fonctions ==
Ligne 453 :
 
 
<sourcesyntaxhighlight lang="c">
 
/* ------------------------------ */
Ligne 507 :
}
 
</syntaxhighlight>
</source>
 
 
Ligne 536 :
* On peut remarquer que les pointeurs de fonctions ont les mêmes types arguments que les fonctions qu'ils vont recevoir.
 
<sourcesyntaxhighlight lang="c">
 
/* ------------------------------ */
Ligne 583 :
}
 
</syntaxhighlight>
</source>
 
 
Ligne 602 :
==== Déclaration d'un tableau de pointeurs de fonctions ====
 
<sourcesyntaxhighlight lang="c">
double (*TrigF[6])(double x) = {cos,sin,tan,atan,asin,acos};
</syntaxhighlight>
</source>
 
* Toutes les fonctions ont la même forme.
Ligne 614 :
==== Exemple d'un appel ====
 
<sourcesyntaxhighlight lang="c">
cos(.5) = TrigF[0](.5)
</syntaxhighlight>
</source>
 
==== Exemple à tester ====
<sourcesyntaxhighlight lang="c">
/* ------------------------------ */
#include <stdio.h>
Ligne 645 :
return 0;
}
</syntaxhighlight>
</source>
 
 
Ligne 656 :
 
==== Résultat dans un fichier ====
<sourcesyntaxhighlight lang="c">
/* ------------------------------ */
#include <stdio.h>
Ligne 688 :
return 0;
}
</syntaxhighlight>
</source>
 
Le résultat :
Ligne 721 :
==== Avec résultat à l'écran ====
 
<sourcesyntaxhighlight lang="c">
/* ------------------------------ */
#include <stdio.h>
Ligne 745 :
return 0;
}
</syntaxhighlight>
</source>
 
 
Ligne 757 :
* Voir listing en fin de page.
 
<sourcesyntaxhighlight lang="c">
double (*Derivate[3])(double (*P_f)(double x),double a,double h) = {fx,Dx_1,Dx_2};
</syntaxhighlight>
</source>
 
 
Ligne 777 :
==== Exemple d'un appel ====
 
<sourcesyntaxhighlight lang="c">
f(x)=Derivate[0](f,x,0.)
</syntaxhighlight>
</source>
 
 
Ligne 789 :
 
 
<sourcesyntaxhighlight lang="c">
/* ------------------------------ */
#include <stdio.h>
Ligne 879 :
}
 
</syntaxhighlight>
</source>
 
<noinclude>