SIF1053 Architecture des ordinateurs Sujets Notions de pointeurs

  • Slides: 38
Download presentation
SIF-1053 Architecture des ordinateurs Sujets • • Notions de pointeurs en C Notions de

SIF-1053 Architecture des ordinateurs Sujets • • Notions de pointeurs en C Notions de base sur les pointeurs Pointeurs et Tableaux (matrices) Arithmétiques sur les pointeurs Chaînes de caractères Tableaux à deux dimensions Tableaux de pointeurs Allocation de mémoire Références: http: //www. cplus. com/doc/tutorial/pointers. html http: //www. cs. cf. ac. uk/Dave/C/node 10. html http: //pweb. netcom. com/~tjensen/ptr/pointers. htm http: //www. commentcamarche. net/c/cpoint. php 3 http: //www. iu. hio. no/~mark/CTutorial. html

Notions de base des pointeurs • Si nous voulons permuter le contenu de deux

Notions de base des pointeurs • Si nous voulons permuter le contenu de deux variables entières x et y, nous permuterons ces variables à l’aide d’une fonction "swap(x, y)" qui permet la permutation des variables x et y (les paramètres). • La version suivante de cette fonction ne fonctionne pas car même si on modifie x et y, les nouvelles valeurs permutées ne sont pas retransmises à la fin de la fonction: void swap(int x, int y) { int temp; temp=x; x=y; y=temp; }

Notions de base des pointeurs • Version fonctionnelle: void swap(int *px, int *py) {

Notions de base des pointeurs • Version fonctionnelle: void swap(int *px, int *py) { int temp; temp=*px; *px=*py; *py=temp; } • Appel à la procédure swap(): int x=3, y=5; swap(&x, &y); // &x: & retourne l'adresse de la variable x // x=5 et y=3 au retour du swa. P()

Notions de base des pointeurs • La plupart des langages de programmation offrent la

Notions de base des pointeurs • La plupart des langages de programmation offrent la possibilité d'accéder aux données dans la mémoire de l'ordinateur à l'aide de pointeurs, Un pointeur est une variable affectée à la valeur d’un adresse. • En C, les pointeurs jouent un rôle primordial dans la définition de fonctions • Sachant que le passage des paramètres en C se fait toujours par valeur, • Les pointeurs permettent de passer une référence sur un objet à une fonction appelée. • Le pointeur permet donc de changer le contenu de variables déclarées dans d'autres fonctions. Ainsi le traitement de tableaux et de chaînes de caractères dans des fonctions serait impossible sans l'utilisation de pointeurs.

Notions de base des pointeurs • Modes d’adressage • Adressage direct : – Dans

Notions de base des pointeurs • Modes d’adressage • Adressage direct : – Dans la programmation, nous utilisons des variables pour stocker des informations. – La valeur d'une variable se trouve à un endroit spécifique dans la mémoire interne (RAM) de l'ordinateur. – Le nom de la variable nous permet alors d'accéder directement à cette valeur. – Donc l’adressage direct permet l’accès au contenu d'une variable par le nom de la variable. • Adressage indirect : – Accès au contenu d'une variable, en passant par un pointeur qui contient l'adresse de la variable. – Donc l’accès à une valeur d’une variable se fait par l’accès préalable au contenu d’un pointeur.

Notions de base des pointeurs • Pointeur • Un pointeur est une variable qui

Notions de base des pointeurs • Pointeur • Un pointeur est une variable qui contient l'adresse d'une autre variable. • En langage C, chaque pointeur est limité à un type de données. Il peut contenir l'adresse d'une variable simple de ce type ou l'adresse d'une composante d'un tableau de ce type. • Si un pointeur P contient l'adresse d'une variable A, on dit que 'P pointe sur A'. • Un pointeur est une variable qui peut 'pointer' sur différentes adresses. Le pointeur pouvant être affecté à différentes adresses. • Le nom d'une variable reste quant à lui toujours lié à la même adresse en RAM.

Notions de base des pointeurs • Opérateurs sur les pointeurs • &<var>: opérateur unaire

Notions de base des pointeurs • Opérateurs sur les pointeurs • &<var>: opérateur unaire qui retourne l’adresse de la variable var. int N; printf("Entrez un nombre entier : "); scanf("%d", &N); // scanf() prend une adresse comme deuxième argument • *<p>: opérateur unaire qui retourne le contenu pointer par p un pointeur. 1 int A=10, B; int *P; P = &A; 2 B = *P; 3 *P = 20; A P 10 &A A 1 20 3 B 10 2

Notions de base des pointeurs • Déclaration des pointeurs • <Type> *<Nom. Pointeur> :

Notions de base des pointeurs • Déclaration des pointeurs • <Type> *<Nom. Pointeur> : déclare un pointeur <Nom. Pointeur> qui être affecté des adresses de variables du type <Type> • Une déclaration comme: int *P ; • Le pointeur P est du type int *, donc P est un pointeur sur des objets int • P contient donc l'adresse d‘objet de type int • Lors de la déclaration d'un pointeur en C, ce pointeur est lié explicitement à un type de données. • Donc la variable pointeur P déclarée comme pointeur sur des int ne peut pas recevoir l'adresse d'une variable d'un autre type que int.

Notions de base des pointeurs • Opérations sur les pointeurs • Les opérateurs *

Notions de base des pointeurs • Opérations sur les pointeurs • Les opérateurs * et & ont la même priorité que les autres opérateurs unaires (la négation !, l'incrémentation ++, la décrémentation --). • Dans une même expression, les opérateurs unaires *, &, !, ++, -- sont évalués de droite à gauche. • Si un pointeur P pointe sur une variable X, alors *P peut être utilisé partout où on peut écrire explicitement X. • Si nous avons: P = &X; Y = X+1 // Y = *P + 1; X = X+10 // *P = *P + 10; X += 2 // *P +=2; ++X // ++(*P); ou ++*P; X++ // (*P)++

Notions de base des pointeurs • Valeur initiales • La valeur numérique 0 (zéro,

Notions de base des pointeurs • Valeur initiales • La valeur numérique 0 (zéro, NULL) est utilisée pour indiquer qu'un pointeur ne pointe 'nulle part‘, sur rien. int *P ; P = 0 ; • Les pointeurs sont aussi des variables et peuvent être utilisés comme telles. Soit P 1 et P 2 deux pointeurs sur des int, alors l'affectation P 1 = P 2; copie le contenu de P 2 vers P 1 pointe alors sur le même objet que P 2. Après les instructions suivantes : int A=20, B; // variable int. &A adresse de A int *P ; // P est un pointeur sur des int P = &A ; B = *P; // Contenu de A affecte a B

Notions de base des pointeurs • Exercise main() { int A = 1, B

Notions de base des pointeurs • Exercise main() { int A = 1, B = 2, C = 3; int *P 1, *P 2; P 1=&A; // P 1 pointe sur A P 2=&C; // P 2 pointe sur C *P 1=(*P 2)++; // A = 3 C = 4 P 1=P 2; // P 1 pointe sur C P 2=&B; // P 2 pointe sur B *P 1 -=*P 2; // C = 2 ++*P 2; // B = 3 *P 1*=*P 2; // C = 6 A=++*P 2**P 1; // A = 24 P 1=&A; // P 1 pointe sur A *P 2=*P 1/=*P 2; // B = 6 return 0; }

Pointeurs et tableaux • int tableau[100]; • &tableau[0] et tableau sont une seule et

Pointeurs et tableaux • int tableau[100]; • &tableau[0] et tableau sont une seule et même adresse. • Donc le nom d'un tableau est un pointeur constant sur le premier élément du tableau. int A[10], B; int *P ; P = A ; // P pointe sur A[0], P = &A[0]; equivalent B = *(P+1); // P+1 pointe sur le contenu de A[1] B = *(P+2); // P+2 pointe sur le contenu de A[2] . . . B= *(P+i); // P+i pointe sur le contenu de A[i]

Pointeurs et tableaux float A[20], float X ; float *P ; ………………… A[0] *(A)

Pointeurs et tableaux float A[20], float X ; float *P ; ………………… A[0] *(A) P = A ; // P pointe sur l’element A[0] X = *(P+9) ; // P+9 pointe sur l’element A[9] • A représente l'adresse de l’élément A[0], • *(A+1) pointe sur le contenu de A[1] • *(A+2) pointe sur le contenu de A[2] . . . • *(A+i) pointe sur le contenu de A[i] A[19] *(A+19)

Pointeurs et tableaux • Il existe une différence fondamentale entre une variable pointeur et

Pointeurs et tableaux • Il existe une différence fondamentale entre une variable pointeur et le nom d'un tableau : – Un pointeur est une variable, sa valeur peut alors changée, des opérations arithmétiques sur ces pointeurs peuvent être effectuées, comme l’affectation P = A ou des incréments P++ ou toutes autres opérations arithmétiques. – Le nom associé à un tableau est une constante, donc des opérations arithmétiques comme A = P ou A++ sont illégales.

Pointeurs et tableaux • Exemple tableau VS pointeur main() { int T[10] = {-3,

Pointeurs et tableaux • Exemple tableau VS pointeur main() { int T[10] = {-3, 4, 0, -7, 3, 8, 0, -1, 4, -9}; int POS[10]; // int * Tptr, *Pptr; int I, J; /* indices courants dans T et POS */ // Tptr = T; Pptr = POS; for (J=0, I=0; I<10; I++) if (T[I]>0) // (*(Tptr+I) > 0) { POS[J] = T[I]; // *(Pptr+J) = *(Tptr+I) J++; } return 0; }

Arithmétiques sur les pointeurs • Affectation d’un pointeur • Soient P 1 et P

Arithmétiques sur les pointeurs • Affectation d’un pointeur • Soient P 1 et P 2 deux pointeurs sur le même type de données, alors l'instruction P 1 = P 2 ; • fait pointer P 1 sur le même objet que P 2. • Addition et soustraction d’une valeur entière • Si le pointeur P pointe sur l'élément A[i] d'un tableau, alors P+n pointe sur A[i+n] et P-n pointe sur A[i-n]. • n correspond au nombre d’emplacements du type de donnée pointé qu’il faut se déplacer à partir de P pour atteindre l’emplacement recherché (P+n). • Ce déplacement (décalage) correspond à un saut de n*sizeof(type de donnée) octets dans la mémoire RAM. • Si P pointe sur des objets float, P+4 pointe alors sur le cinquième élément de type float, donc 16 octets à partir du début du vecteur de float pointé par P.

Arithmétiques sur les pointeurs • Incrément/Décrément d’un pointeur • Si P pointe sur l'élément

Arithmétiques sur les pointeurs • Incrément/Décrément d’un pointeur • Si P pointe sur l'élément A[i] d'un tableau, alors après l'instruction : – P = &A[i]; – P++ ; // P pointe sur A[i+1] – P = &A[i]; – P+= n ; // P pointe sur A[i+n] – P = &A[i]; – P-- ; // P pointe sur A[i-1] – P = &A[i]; – P-= n ; // P pointe sur A[i-n]

Arithmétiques sur les pointeurs • Rappels importants: • L'addition, la soustraction, l'incrémentation et la

Arithmétiques sur les pointeurs • Rappels importants: • L'addition, la soustraction, l'incrémentation et la décrémentation sur les pointeurs sont seulement définies à l'intérieur d'un tableau. Si l'adresse formée par le pointeur et l'indice sort du domaine du tableau, alors le résultat n'est pas défini et peut causer des erreurs d’accès mémoire difficiles à détecter. • Ces opérations arithmétiques sont valables également pour les pointeurs qui pointent vers des variables indépendantes, i. e. , ne faisant pas partie d’un tableau. À titre d’exemple, si P pointe sur un élément de type quelconque, alors P+1 pointe sur l’octet qui suit immédiatement cet élément. Exemple : si P pointe sur un short int alors P+1 sera égale à l’adresse référencée par P plus 2 (puisque la taille d’un short int est 2 octets).

Arithmétiques sur les pointeurs • Exemples d’utilisations d’opérations arithmétiques int A[10] ; int *P

Arithmétiques sur les pointeurs • Exemples d’utilisations d’opérations arithmétiques int A[10] ; int *P ; P = A+9 ; // P pointe sur le dixieme element de A P = A+11 ; // P pointe au-dela du vecteur A de 8 octets P = A-1 ; // P pointe 4 octets avant le debut de A

Arithmétiques sur les pointeurs • Comparaisons de pointeurs (opérateurs relationnels) • On peut comparer

Arithmétiques sur les pointeurs • Comparaisons de pointeurs (opérateurs relationnels) • On peut comparer deux pointeurs par <, >, <=, >=, ==, !=. • La comparaison de deux pointeurs qui pointent dans le même tableau est équivalente à la comparaison des indices correspondants. (Si les pointeurs ne pointent pas dans le même tableau, alors le résultat est donné par leurs positions relatives dans la mémoire).

Arithmétiques sur les pointeurs • Autre exemple int A[] = {12, 23, 34, 45,

Arithmétiques sur les pointeurs • Autre exemple int A[] = {12, 23, 34, 45, 56, 67, 78, 89, 90}; int *P, *P 2, B; P = A; B = *P+2; // B = 14 = A[0] + 2 B = *(P+2); // B = 36 = A[2] + 2 P 2 = &P+1; // P 2 pointe sur A[1] P 2 = &A[4]-3; // P 2 pointe sur A[1] P 2 = A+3 ; // P 2 pointe sur A[3] B = &A[7]-P; // B = 7 P 2 = P+(*P-10) ; // P 2 pointe sur A[2] B = *(P+8)-A[7]); // B = 23;

Pointeurs (chaînes de caractères) • Initialisation de pointeur sur des chaînes de caractères •

Pointeurs (chaînes de caractères) • Initialisation de pointeur sur des chaînes de caractères • Une chaîne de caractères est stockée dans un tableau linéaire de "char". La chaîne de caractères est complétée par le caractère NULL, le zéro binaire (''). char x[]="abcd"; // x est un tableau de 5 caractères, // x[4]=='' et aussi x[4]==0 • Un pointeur de char peut être initialisé lors de sa déclaration si on lui affecte l'adresse d'une chaîne de caractères constante : char *B = "PRO 1027" ;

Pointeurs (chaînes de caractères) • Initialisation de pointeur sur des chaînes de caractères •

Pointeurs (chaînes de caractères) • Initialisation de pointeur sur des chaînes de caractères • Il existe une différence importante entre les deux déclarations : char A[] = "PRO 1027"; /* un tableau */ char *B = "PRO 1027"; /* un pointeur */ • A est un tableau qui a exactement la grandeur pour contenir la chaîne de caractères et le caractère de fin ''. Les caractères de la chaîne peuvent être changés, mais le nom A va toujours pointer sur la même adresse en mémoire. • B est un pointeur qui pointe sur une chaîne de caractères constante stockée quelque part en mémoire. Le pointeur peut être modifié et pointer sur autre chose. La chaîne constante peut être lue, copiée ou affichée, mais pas modifiée.

Pointeurs (chaînes de caractères) • Initialisation de pointeur sur des chaînes de caractères char

Pointeurs (chaînes de caractères) • Initialisation de pointeur sur des chaînes de caractères char *A = "Chaîne courte" ; char *B = "Chaîne plus longue"; A = B; // A pointe sur la meme chaine que B • Les tableaux de caractères permettront de déclarer des chaînes de caractères que nous voulons modifier. • Les pointeurs de char seront utilisés pour manipuler des chaînes de caractères constantes (dont le contenu ne change pas).

Tableaux à deux dimensions • Un tableau M à deux dimensions peut être définis:

Tableaux à deux dimensions • Un tableau M à deux dimensions peut être définis: int M[4][10] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, {20, 21, 22, 23, 24, 25, 26, 27, 28, 29}, {30, 31, 32, 33, 34, 35, 36, 37, 38, 39} }; • Le nom du tableau M représente l'adresse du premier élément du tableau et pointe sur le tableau M[0] qui a la valeur {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}. M[0] pointe sur la rangée 0. • L'expression (M+1) est l'adresse du deuxième élément du tableau M et pointe sur M[1] qui a la valeur {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}. M[1] pointe sur la rangée 1. • L'arithmétique des pointeurs qui respecte automatiquement les dimensions des éléments conclut logiquement que M+i désigne l'adresse du tableau M[i].

Tableaux à deux dimensions • Exemple pointeur VS tableau 2 D int M[4][10] =

Tableaux à deux dimensions • Exemple pointeur VS tableau 2 D int M[4][10] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, {20, 21, 22, 23, 24, 25, 26, 27, 28, 29}, {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}}; int *P, B; P = (int *)M; /* conversion forcée */ B = *(P+(2*10) + 2); // B = M[2][2] • Sachant que M est stockée ligne par ligne, il est maintenant possible traiter M à l'aide du pointeur P comme un tableau unidimensionnel de dimension 4*10.

Tableaux à deux dimensions • Exemple pointeur VS tableau 2 D ( des éléments

Tableaux à deux dimensions • Exemple pointeur VS tableau 2 D ( des éléments de M) int M[4][10] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, {20, 21, 22, 23, 24, 25, 26, 27, 28, 29}, {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}}; int *P, som=0; P = (int *)M; for(int i=0; i<4; i++) for(int j=0; j<10; j++) som += M[i][j]; // P[i*10+j] ou *(P+(i*10)+j)

Tableaux à deux dimensions • Pour référencer un tableau 2 D à l'aide de

Tableaux à deux dimensions • Pour référencer un tableau 2 D à l'aide de pointeurs, nous avons besoin de quatre données : • L'adresse du premier élément du tableau converti dans le type correspondant aux éléments du tableau • La longueur d'une ligne réservée en mémoire • Le nombre d'éléments effectivement utilisés dans une ligne • Le nombre de lignes effectivement utilisées

Tableaux de pointeurs • Déclaration: <Type> *<Nom. Tableau>[<N>] déclare un tableau <Nom. Tableau> de

Tableaux de pointeurs • Déclaration: <Type> *<Nom. Tableau>[<N>] déclare un tableau <Nom. Tableau> de <N> pointeurs sur des données de type <Type>. double *A[10]; déclare un tableau de 10 pointeurs sur des objets de type double dont les adresses et les valeurs ne sont pas encore définies. • Les tableaux de pointeurs sont souvent utilisés pour mémoriser de façon économique des chaînes de caractères de différentes longueurs. Ex: Le tableaux char *argv[] passé en argument à la fonction main() d’un programme C.

Tableaux de pointeurs • Nous pouvons initialiser les pointeurs d'un tableau sur char par

Tableaux de pointeurs • Nous pouvons initialiser les pointeurs d'un tableau sur char par les adresses de chaînes de caractères constantes. char *MOIS[] = {"janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"}; déclare un tableau MOIS[] de 12 pointeurs de char. Chacun des pointeurs est initialisé avec l'adresse de l'une des 12 chaînes de caractères. On peut afficher ces 12 chaînes de caractères en fournissant les adresses contenues dans le tableau MOIS aux fonction d’affichage printf() ou puts() : int i; for (i=0; i<12; i++) printf("%sn", MOIS[i]) ; // puts(MOIS[i])

Tableaux de pointeurs • Comme MOIS[i] est un pointeur de char, on peut afficher

Tableaux de pointeurs • Comme MOIS[i] est un pointeur de char, on peut afficher les premières lettres des mois en utilisant l'opérateur * 'contenu de' : int i; for (i=0; i<12; i++) printf("%cn", *MOIS[i]); • L'expression MOIS[i]+j correspond à la jième lettre de la iième chaîne. int i; for (i=0; i<12; i++) printf("%cn", *(MOIS[i]+2));

Tableaux de pointeurs • Formatage des sorties avec printf()

Tableaux de pointeurs • Formatage des sorties avec printf()

Tableaux de pointeurs • Formatage des sorties avec printf()

Tableaux de pointeurs • Formatage des sorties avec printf()

Tableaux de pointeurs • Formatage des sorties avec printf()

Tableaux de pointeurs • Formatage des sorties avec printf()

Allocation dynamique • Lorsque nous ne connaissons pas d’avance la dimension des tableaux que

Allocation dynamique • Lorsque nous ne connaissons pas d’avance la dimension des tableaux que nous devons utiliser dans notre application, Nous devons trouver des moyens pour réserver et libérer de la mémoire au fur et à mesure que nous en avons besoin. Nous parlons alors de l'allocation dynamique de la mémoire. • Déclaration statique de données • Chaque variable dans un programme utilise un certain nombre d'octets en mémoire. • Les exemples présentés utilisaient un mode de réservation de mémoire automatique appliquée lors des déclarations des données. • Dans tous ces cas, le nombre d'octets à réserver était déjà connu pendant la compilation. Ce qui correspond à la déclaration statique des variables. float A, B, C; // 3 X 4 octets short D[10][20]; // 10 X 2 octets char E[] = {"PRO 1027"}; // 8 octets char F[][10] = {"un", "deux", "trois", "quatre"}; // 4 X 10 octets

Allocation dynamique • Déclaration de pointeurs double *G ; // machine 32 bits, 4

Allocation dynamique • Déclaration de pointeurs double *G ; // machine 32 bits, 4 octets char *H ; // machine 32 bits, 4 octets float *I[10] ; // machine 32 bits, 10 X 4 octets • La fonction malloc() de la bibliothèque <stdlib. h> nous aide à localiser et à réserver de la mémoire au cours d'un programme. • malloc( <N> ) : retourne l'adresse d'un bloc en mémoire de <N> octets libres ou la valeur zéro s'il n'y a pas assez de mémoire. • Supposons que nous avons besoin d'un bloc de mémoire pour une image de niveaux de gris (0. . 255) de 1024 X 1024 unsigned char *img; img = malloc(1024*1024) ; // retourne un bloc de 1024 X 1024 octets • Si nous voulons réserver de la mémoire pour des données d'un type dont la grandeur varie d'une machine à l'autre, nous avons besoin de la grandeur effective d'une donnée de ce type. • L'opérateur sizeof() nous aide alors à préserver la portabilité du programme.

Allocation dynamique sizeof <var> fournit la grandeur de la variable <var> sizeof <const> fournit

Allocation dynamique sizeof <var> fournit la grandeur de la variable <var> sizeof <const> fournit la grandeur de la constante <const> sizeof (<type>) fournit la grandeur pour un objet du type <type> • Exemples: short A[10]; char B[5][10]; printf(“n valeur retourne = %d”, sizeof(A); // 10 X 2 printf(“n valeur retourne = %d”, sizeof(B); // 5 X 10 X 1 printf(“n valeur retourne = %d”, sizeof(“PRO 1027" ); // 8 printf(“n valeur retourne = %d”, sizeof(float ); // 4 printf(“n valeur retourne = %d”, sizeof(double ); // 8

Allocation dynamique • Libération de la mémoire • Quand un bloc de mémoire que

Allocation dynamique • Libération de la mémoire • Quand un bloc de mémoire que nous avons réservé à l'aide de malloc() n’est plus utilisé, nous pouvons alors le libérer à l'aide de la fonction free() de la bibliothèque <stdlib. h>. free( <Pointeur> ) • Cette fonction libère le bloc de mémoire désigné par le pointeur <Pointeur>. • La fonction free() peut aboutir à un désastre si on essaie de libérer de la mémoire qui n'a pas été allouée par malloc(). • La fonction free() ne change pas la valeur (adresse) du pointeur; il est donc conseillé d'affecter la valeur zéro au pointeur immédiatement après avoir libéré le bloc de mémoire qui y était attaché. • Si nous ne libérons pas explicitement la mémoire à l'aide de la fonction free(), alors elle est libérée automatiquement à la fin du programme. • MAIS entre temps, un programme qui ne libère pas sa mémoire inutilisée peut occasionner des fuites de mémoire (memory leaks).