Ce cours/TD/TP est à réaliser à l’aide de code::block
Disponible gratuitement ici : https://codeblocks.org/downloads/26
Souc Windows , télecharger codeblocks-xx.yymingw-setup.exe (xx.yy est le numéro de la dernière version) code ::block est préinstallé sur les distributions Linux type Debian (Ubuntu, Mint ...)
Cette version de code::block dispose du compilateur c/c++ mingw sur windows et gcc sous Linux ainsi que du debugger GDB (Gnu Debugger) qui permet de poser des points d’arrêt, d’avancer un programme en pas à pas et de consulter les variables.
Prérequis :
Connaissances élémentaires du langage C. main, fonctions, variables, types, printf.
Création d’un projet dans code::block en langage C.
Utilisation du debugger GDB
1 Langage C/C++ , adresses et données
La mémoire d’une machine (ordinateur, microcontrôleur) est organisée en octets. Chaque octet possède une adresse.
Les données occupent un ou plusieurs octets suivant leur type :
Type de donnée Signification Taille (en octets) Plage de valeurs acceptée
char Caractère (ASCII) 1 -128 à 127
unsigned char Caractère non signé 1 0 à 255
short int Entier court 2 -32 768 à 32 767
unsigned short int Entier court non signé 2 0 à 65 535
int Entier 2 (sur processeur 16 bits)
4 (sur processeur 32 bits)
-32 768 à 32 767
-2 147 483 648 à 2 147 483 647 unsigned int Entier non signé 2 (sur processeur 16 bits)
4 (sur processeur 32 bits) 0 à 65 535 0 à 4 294 967 295
long int Entier long 4 -2 147 483 648 à 2 147 483 647
unsigned long int Entier long non signé 4 0 à 4 294 967 295
float Flottant (réel) 4 3.4*10-38 à 3.4*1038
double Flottant double 8 1.7*10-308 à 1.7*10308
long double Flottant double long 10 3.4*10-4932 à 3.4*104932 Les variables peuvent être :
Globales : déclarées en début de programme, elles existent durant toute la durée d’exécution du programme et sont accessibles depuis n’importe quelle partie du programme.
Locales : déclarées dans une fonction, elles n’existent que lors de l’exécution de la fonction et donc accessibles que depuis la fonction. Les variables locales permettent d’améliorer l’occupation de la mémoire.
Les pointeurs sont des variables qui contiennent l’adresse d’une autre variable.
Un pointeur est déclaré avec une * :
int *p ; représente un pointeur sur des entiers double *d ; représente un pointeur sur des réels
L’opérateur * permet d’accéder au contenu de la donnée pointé par le pointeur.
L’opérateur & permet de récupérer l’adresse d’une variable.
Remarque : pour récupérer l’adresse d’un tableau le & n’est pas nécessaire, le nom du tableau représentant l’adresse de celui-ci.
int i,j ; // i et j sont des entiers
int *p ; // p est un pointeur sur des entiers
p=&i ; // p contient l’adresse de i, on dit que pointe sur i
j=*p ; // j est égal au contenu de l’adresse lointée par p, ce qui revient à j=i ;
Deux nouvelles variables de type pointeur (pi et pc) sont créés, les adresses des autres variables sont modifiées.
La fenêtre Watches du debugger donne les valeurs par défaut en décimal. Cliquer-droit sur une valeur puis
« properties » puis choisir la base d’affichage hexadecimal.
On voit ici les valeurs de iglo, jglo,, xloc, yloc et les valeurs des deux pointeurs qui contiennent les adresses de iglo et xloc,
#include <stdio.h>
#include <stdlib.h>
int *pi; // un pointeur sur des entiers char *pc; // un pointeurs sur des caractères char mess[]="Bonjour"; // une chaine
int iglo; // variables globales int jglo;
int main() {
char xloc,yloc; // variables locales
iglo=0x12345678; // les entiers sont codés sur 32bits jglo=0xABCDEF09;
xloc=0x11; // les char sont codés sur 8 bits yloc=0x22;
pi=&iglo; // pi contient l'adresse de iglo pc=&xloc; // pc contient l'adresse de xloc printf("Adresse de iglo pi= %p\n",pi);
printf("Adresse de xloc pc= %p\n",pc);
return 0;
}
2 Un grand classique, la fonction « echange »
On désire réaliser une fonction qui échange les valeurs de deux variables.
Une première approche consiste à réaliser ce programme qui teste la fonction échange :
L’exécution de ce programme donne ce résultat, visiblement il ne fonctionne pas :
Placer un point d’arrêt sur le premier printf, lancer le debugger, visualiser x,x,i,j,k.
Dérouler le programme en pas à pas avec step into qui permet d’entrer dans les fonctions.
Constater lors de l’appel de la fonction echange, le transfert des valeurs de x et y dans i et j, puis l’échange de i avec j. Mais au retour x et y n’ont pas changés.
Cela est dû au principe du transfert de paramètres par valeur du C/C++.
Le seul moyen de réaliser la fonction échange est de transférer non pas les valeurs de x et y mais leurs adresses et donc d’utiliser des pointeurs.
#include <stdio.h>
#include <stdlib.h>
void echange(int i, int j) {
int k;
k=i;
i=j;
j=k;
}
int main() {
int x=1;
int y=2;
printf("x= %d y=%d\n",x,y);
echange(x,y);
printf("x= %d y=%d\n",x,y);
return 0;
}
La fonction échange qui fonctionne
Ajouter dans Watches, *i et *j qui représentent les valeurs de x et y.
On voit ici le résultat.
#include <stdio.h>
#include <stdlib.h>
void echange(int *i, int *j) // les paramètres sont des pointeurs {
int k;
k=*i; // k = le contenu de l’adresse pointée par i donc x *i=*j; // le contenu de i = le contenu de j donc x=y *j=k; // le contenu de j dinc y = k qui vaut x }
int main() {
int x=1;
int y=2;
printf("x= %d y=%d\n",x,y);
echange(&x,&y); // on transmet maintenant les adresses de x et y printf("x= %d y=%d\n",x,y);
return 0;
}
3 Pointeurs sur les tableaux
Le programme ci-dessous montre l’utilisation d’un pointeur sur une chaîne de caractère.
Exécuter le programme en pas à pas, visualiser et interpréter les valeurs des adresses des chaînes de caractères, de p et j.
#include <stdio.h>
#include <stdlib.h>
char jour1[]={"lundi"};
char jour2[]={"mardi"};
char jour3[]={"mercredi"};
char jour4[]={"jeudi"};
char jour5[]={"vendredi"};
char jour6[]={"samedi"};
char jour7[]={"dimanche"};
void affJour(char *j) {
printf("Nous sommes %s\n",j);
}
int main() {
char *p; // p est un pointeur sur des char p=jour4; //pas de & pour un tableau
affJour(p);
p=jour2;
affJour(p);
p=jour6;
affJour(p);
return 0;
}
4 Pointeurs de pointeurs
Le programme précédent ne permet pas d’afficher en boucle les jours de la semaine. Il est possible de déclarer des tableaux de chaines de caractère. Chaque chaine étant repérée par une adresse (accessible par un pointeur), on peut indexer les chaines par un pointeur de pointeur.
#include <stdio.h>
#include <stdlib.h>
// deux tableaux de pointeurs de chaines de caratères
char *jour[]={"lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"};
char
*mois[]={"janvier","fevrier","mars","avril","mai","juin","juillet","aout","septembre","octobre","novemb re","decembre"};
// un pointeur sur des pointeurs de char char **pp;
// affiChaine reçoit un pointeur de pointeurs de char (donc d'une chaine) et le nombre de pointeurs de char (de chaines) à traiter
void affChaine(char **ptc, int n) {
int i;
for(i=0;i<n;i++) {
printf("%s ",*(pp+i));
}
printf("\n");
}
int main() {
pp=jour+2; // pp pointe sur le pointeur de la chaine jour + 2 soit mercredi printf("%s\n",*pp);
pp=mois+2; // pp pointe sur le pointeur de la chaine mois + 2 soit mars printf("%s\n",*pp);
pp=jour; // pp pointe sur le premier pointeur *jour affChaine(pp,7);
pp=mois; // pp pointe sur le premier pointeur *mois affChaine(pp,12);
return 0;
}
5 Énumérations
Une énumération (enum) est une sorte de « define » qui numérote des équivalences. Une énumération définit un nouveau type.
Dans le programme suivant lundi=0 mardi=1 etc.… L’utilisation de pointeurs s’accompagne souvent d’énumérations.
Un type énuméré dans une boucle :
#include <stdio.h>
#include <stdlib.h>
// jourSemaine est nouveau type de données // lundi=0, mardi=1 etc...
enum jourSemaine {
lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche };
// les chaines de caractères des jours de la semaine
char *jour[]={"lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"};
char **pp;
int main() {
enum jourSemaine aujourdhui; // aujourdhui est de type jouSemaine aujourdhui=mercredi; // donc aujourdhui = 2
pp=jour+aujourdhui; // donc pp pointe sur la chaine *jour[2] c'est à dire "mercredi"
printf("%s\n",*pp);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
// jourSemaine est nouveau type de données // lundi=0, mardi=1 etc...
enum jourSemaine {
lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche };
// les chaines de caractères des jours de la semaine
char *jour[]={"lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"};
char **pp;
int main() {
enum jourSemaine aujourdhui; // aujourdhui est de type jouSemaine for(aujourdhui=lundi;aujourdhui<=dimanche;aujourdhui++) {
pp=jour+aujourdhui; // donc pp pointe sur la chaine *jour[2] c'est à dire "mercredi"
printf("%s\n",*pp);
} return 0;
}
6 Pointeurs sur des fonctions
Il est possible de créer un pointeur sur une fonction. Cela permet de passer l’adresse d’une fonction en paramètre d’une fonction.
Dans l’exemple ci-dessous la fonction affiche reçoit en paramètre un nombre a et un pointeur sur une fonction. Elle appelle la fonction pointée avec le paramètre a, reçoit en retour le résultat de l’opération.
La déclaration d’un pointeur sur une fonction est similaire à la déclaration d’une fonction retournant un pointeur. On ajoute juste des parenthèses pour spécifier que le pointeur est sur la fonction. Le non de la variable d’entrée n’est pas spécifié, seulement son type.
Une fonction qui reçoit un entier et retourne un pointeur sur un entier → int *pf(int a) ; Un pointeur sur une fonction qui reçoit un entier et retourne un entier → int (*pf)(int) ;
Exécuter le programme en pas à pas et visualiser les passages par les fonctions triple et quadruple
#include <stdio.h>
int triple(int a) {
printf("Calcul de %d x 3 ",a);
return a * 3;
}
int quadruple(int a) {
printf("Calcul de %d x 4 ",a);
return a * 4;
}
void affiche(int a,int (*pf)(int)) // pointeur sur une fonction qui reçoit un int et renvoie un int {
printf(" -> %d\n",(*pf)(a));
}
int main(void) {
affiche(3, &triple); // passage en paramètre de l'adresse de la fonction triple
affiche(3, &quadruple); // passage en paramètre de l'adresse de la fonction quadruple return 0;
}