Aller au contenu

Programmation C source/pointeurs

Un livre de Wikilivres.
Programmation C source
Programmation C++
Programmation C++
Sommaire
Modifier ce modèle

$

#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;
}