Programmation C source/pointeurs
Apparence
$
#include <stdio.h>
/* inclut le fichier stdio.h
dans stdio.h on trouve entre autre
- la fonction printf
- NULL l'adresse mémoire nulle
cela me permet de les utiliser */
#include <stddef.h>
/* inclut le fichier stddef.h
dans stddef.h on trouve entre autre
- ptrdiff_t un type de variable qui est garanti capable
de recevoir le résultat de la différence entre
2 pointeurs */
#include <stdlib.h>
/* inclut le fichier stdlib.h
dans stdlib.h on trouve entre autre
- la fonction malloc alloue/réserve de la mémoire
- la fonction free libère la mémoire allouée/réservée
- size_t un type de variable pour stocker la taille en char
d'un objet (une variable, un tableau, etc.) */
/* fonction main,
point d'entrée du programme,
executée pour démarrer le programme par le système d'exploitation */
int main(void)
{
/*
Pour comprendre les pointeurs en langage C, il faut comprendre
comment on utilise la mémoire vive de l'ordinateur.
Lorsque je crée une variable dans mon programme, un emplacement
en mémoire vive de la bonne taille est réservé.
La mémoire vive se représente en fait sous la forme d'un tableau
de n cases et chaque case a une adresse de 1 à n.
Au lieu d'utiliser directement les adresses, on utilise des noms
de variable en langage C.
Un pointeur est une variable qui stocke non pas une valeur mais
l'adresse d'une autre variable.
*/
printf("----------------------------------------\n");
printf(" Partie 1 : Adresse (ou référence)\n");
printf("----------------------------------------\n");
int i;
// déclaration d'un entier i
printf("adresse mémoire de i : %p\n", &i);
/* affiche : adresse mémoire de i :
puis une adresse : %p
et un retour à la ligne : \n
quelle adresse ? l'adresse mémoire de i : &i */
printf("\n----------------------------------------\n");
printf(" Partie 2 : Déclaration de pointeurs\n");
printf("----------------------------------------\n");
int * pointeur1;
// déclaration d'un pointeur sur un entier
char * pointeur2;
// déclaration d'un pointeur sur un char
int * pointeur3, * pointeur4;
// déclaration de 2 pointeur sur des entiers
int * pointeur5, variable1;
/* piège : déclaration d'un pointeur sur un entier
et d'une variable entière */
pointeur4 = NULL;
// initialise le pointeur4 à l'adresse mémoire nulle
pointeur5 = &i;
// initialise le pointeur5 à l'adresse de i
printf("\n----------------------------------------\n");
printf(" Partie 3 : Déréférencement\n");
printf(" (ou Accéder à la valeur pointée)\n");
printf("----------------------------------------\n");
/*
Le pointeur pointeur5 stocke une adresse, l'adresse de i.
Pour accéder à la valeur de i en passant par pointeur5,
on utilise l'opérateur * qui n'est pas ici "multiplier"
mais "déréférencement".
On demande en fait d'aller à l'adresse indiquée par la
valeur de pointeur5 et de lire la valeur
qui sera celle de i dans notre cas.
*/
int variable2 = 10;
// déclare et initialise un entier variable2 à la valeur 10
printf("variable2 = %d\n", variable2);
printf("adresse de variable2 = %p\n", &variable2);
int * pointeur6 = &variable2;
/* déclare et initialise pointeur6 (un pointeur sur un entier)
à l'adresse de variable2 */
printf("valeur de pointeur6 (une adresse)= %p\n", pointeur6);
printf("déréférencement, accès à la valeur pointée = %d\n", *pointeur6);
*pointeur6 = 20;
// modifie la valeur de variable2 à 20 via pointeur6
printf("variable2 = %d\n", variable2);
printf("valeur de variable2 via pointeur6 = %d\n", *pointeur6);
printf("\n----------------------------------------\n");
printf(" Partie 4 : Addition, soustraction\n");
printf(" d'une valeur entière\n");
printf(" sur un pointeur\n");
printf("----------------------------------------\n");
int tableau[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int * p;
/* 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.
(en clair il ne faut pas utiliser tableau[10]
pour accéder à la valeur ou la modifier) */
for (p = tableau; p < &tableau[10]; p++) // p++ en clair p = p + 1
{
printf("*p = %d\n", *p);
}
/* À 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.
(en clair il ne faut pas utiliser *p alors que p = &tableau[10]
pour accéder à la valeur ou la modifier) */
printf("\n----------------------------------------\n");
printf(" Partie 5 : Soustraction de deux");
printf("\n pointeurs de même type\n");
printf(" et opérateur []\n");
printf("----------------------------------------\n");
int tableau1[8];
int autre_tableau2[5];
int * p1 = &tableau1[7];
int * p2 = &tableau1[2];
ptrdiff_t difference1 = p1 - p2;
/* on calcule la différence entre 2 pointeurs
(combien d'objet de type int y a t-il entre les deux pointeurs)
7 - 2 = 5 */
printf("différence1 = %d\n", difference1);
ptrdiff_t difference2 = p2 - p1;
// p2 -p1 est l'inverse de p1 -p2 donc le résultat est -5
printf("différence2 = %d\n", difference2);
p2 = &autre_tableau2[3];
ptrdiff_t difference3 = p1 - p2;
/* à ne pas faire !
la différence s'effectue mais n'a aucun sens
p2 a l'adresse d'un élément d'un tableau différent de p1 */
printf("différence3 = %d\n", difference3);
int var = 12;
int * p3 = &var;
printf("valeur à l'adresse contenu par *p3 = %d\n", *p3);
/* 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.
On peut accéder à la valeur de a via p3[0] ou *p3 */
printf("valeur à l'adresse contenu par p3[0] = %d\n", p3[0]);
// Mais attention ! p3[1] est indéfini.
printf("\n----------------------------------------\n");
printf(" Partie 6 : Arithmétique\n");
printf("----------------------------------------\n");
char chaine1[] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r', '\0' };
char chaine2[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', '\0' };
printf("chaine2 = %s\n", chaine2);
char * d = chaine2; // d comme destination
char * s = chaine1; // s comme source
while( *d++ = *s++ ); // résultat on a copié s dans d
/* Suivant la priorité des opérateurs while( *d++ = *s++ );
s'écrit plus clairement while( ( *(d++) = *(s++) ) ) { }
on remarque que while n'effectue rien dans la boucle : { } ou ;
tout se passe dans la condition
dans l'ordre on effectue
d++ : on postincrémente d
c'est à dire l'opérateur ++ postfixé renvoit d puis incrémente d)
*(d++) : on déréférence
c'est à dire on accède pas à l'adresse
mais à la valeur pointée par d et non d + 1
(car d et pas d + 1 a été renvoyé par l'opérateur ++ postfixé)
idem pour *(s++)
*(d++) = *(s++) : on affecte *d = *s
while( ( *(d++) = *(s++) ) ) { } : tant que ( *(d++) = *(s++) ) != 0
c'est à dire tant que *d != 0
(0 = faux)
on boucle */
printf("chaine2 = %s\n", chaine2);
int entier1;
int tableau3[] = { 0, 1, 2 };
int * pointeur7;
pointeur7 = tableau3;
// pointeur7 = &tableau3[0];
entier1 = *pointeur7++;
/* entier1 = *(pointeur7++);
par ordre pointeur7++ mais ++ postfixé renvoie &tableau3[0]
et affecte pointeur7 = &tableau3[1] à la fin de l'expression
ainsi entier1 = *(&tableau3[0])
donc entier1 = tableau3[0] */
printf("entier1 = %d\n", entier1);
entier1 = *++pointeur7;
/* Incrémente d'abord le pointeur, puis déréférence la nouvelle adresse pointée
pointeur7 = &tableau3[1]
++pointeur7 donne pointeur7 = &tableau3[2]
donc entier1 = 2 */
printf("entier1 = %d\n", entier1);
entier1 = ++*pointeur7;
/* Incrémente la valeur pointée par pointeur7, puis affecte le résultat à entier1
*pointeur7 = tableau3[2]
*pointeur7 = *pointeur7 + 1
entier1 = 3 */
printf("entier1 = %d\n", entier1);
entier1 = (*pointeur7)++;
/* Affecte la valeur pointée par pointeur7 et incrémente cette valeur
(*pointeur7)++ donne *pointeur7 = 3 + 1
dans (*pointeur7)++ ++ est postfixé donc renvoie 3
entier1 = 3 */
printf("entier1 = %d\n", entier1);
printf("tableau3[2] = %d\n", tableau3[2]);
printf("\n----------------------------------------\n");
printf(" Partie 7 : Tableau dynamiques\n");
printf("----------------------------------------\n");
/* fonction qui alloue n blocs de taille taille
et qui retourne l'adresse de début */
int * alloue_tableau(int n, size_t taille)
{
return malloc(n * taille);
/* alloue n * taille et retourne l'adresse de début
ou l'adresse nulle en cas d'echec */
}
int * tableau4 = alloue_tableau(256, sizeof *tableau4);
/* Cet exemple alloue un tableau de 256 cases,
de tableau4[0] à tableau4[255]
sizeof donne la taille en char de *tableau4
(combien de char dans un int ? 2) necessaire pour malloc
sizeof *tableau4 est toujours remplacé lors de la compilation
par la valeur dans note cas par 2 */
if (tableau4 != NULL)
{
/* opérations sur le tableau */
/* ... */
free(tableau4);
// libère la mémoire allouée
}
printf("\n----------------------------------------\n");
printf(" Partie 8 : Tableau dynamiques\n");
printf(" à plusieurs dimensions\n");
printf("----------------------------------------\n");
int ** matrice;
/* Déclare un tableau dynamiques à plusieurs dimensions.
Pour déclarer un tel tableau, on déclare des pointeurs sur des pointeurs */
#define LIGNES 4
#define COLONNES 5
matrice = malloc(sizeof *matrice * LIGNES);
// on alloue 4 entrées une pour chaque ligne
int j;
for (j = 0; j < LIGNES; j++)
{
matrice[j] = malloc(sizeof **matrice * COLONNES);
// on alloue 5 entrées pour les 5 colonnes par ligne
}
int k;
for(k = 0; k < LIGNES; k++)
{
free(matrice[k]);
// on libère une ligne soit 5 colonnes
}
free(matrice);
// on libère les 4 entrées une pour chaque ligne
printf("\n----------------------------------------\n");
printf(" Partie 9 : Passage par adresse\n");
printf("----------------------------------------\n");
/* Ce code échange le contenu de deux variables */
void inverse(int * a, int * b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int a = 5;
int b = 2;
printf("a = %d, b = %d.\n", a, b);
/* On passe à 'inverse' les adresses de a et b. */
inverse(&a, &b);
printf("a = %d, b = %d.\n", a, b);
inverse(&a, &b);
printf("a = %d, b = %d.\n", a, b);
printf("\n----------------------------------------\n");
printf(" Partie 10 : Pointeurs vers fonctions\n");
printf("----------------------------------------\n");
void (*pointeur_fonction)(int * a, int * b);
// on déclare un pointeur de fonction
pointeur_fonction = &inverse;
/* On initialise pointeur_fonction à la fonction inverse.
Est en fait équivalent à : */
pointeur_fonction = inverse;
(*pointeur_fonction)(&a, &b);
/* On execute la fonction, dans ce cas inverse.
Ou plus simplement, mais moins logique syntaxiquement */
pointeur_fonction(&a, &b);
// Quitter en retournant un entier 0
return 0;
}