Programmation C source/pointeurs
$
/* 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 */
/* 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 */
/* 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++ );
 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;