Date de première publication : 2014/09/26
Dans ce TP de C, nous allons nous intéresser à différents aspects des pointeurs.
Opérations sur les pointeurs
int main()
{
int i , *ptri = &i;
char c1 = '1', *ptrc1 = &c1;
printf("ptri = %u ptrc1 = %u \n",ptri, ptrc1);
printf("ptri = %x ptrc1 = %x \n",ptri, ptrc1);
// seule la version suivante ne genere pas d'avertissement
printf("ptri = %p ptrc1 = %p \n",ptri, ptrc1);
printf("*ptri = %d et *ptrc1=%c", *ptri, *ptrc1);
ptri++;
ptrc1++;
printf("ptri = %u ptrc1 = %d \n",ptri, ptrc1);
// cela permet de voir la taille d'un int et d'un char en memoire
// sizeof(int) sizeof(char)
return 0 ;
}
Après avoir testé ce code et ajouté le bon include, vous allez l’adapter en faisant les modifications suivantes :
- Ajouter une nouvelle variable de type
double
(d
) et un pointeur sur cette variable (ptrd
). - Afficher pour chaque pointeur, la valeur pointée, l’adresse contenue dans le pointeur et l’adresse du pointeur (attention à ne pas confondre ces 2 dernières).
- Ajouter
2
au pointeur sur la variabled
et affichez à nouveau la valeur contenue dans ce pointeur (constatez et comprenez l’écart entre cette valeur et la valeur précédente). - Ensuite, déclarez une variable de type caractère (
c2
) initialisée à'2'
et un pointeur sur cette variable (ptrc2
).
Attention, 2
, "2"
et '2'
sont des choses complètement différentes !
- Échanger le contenu de
c1
etc2
en utilisant les pointeurs
Vous devez compiler avec les options de compilation –Wall –Wextra
, vous avez donc des
avertissements concernant l’affichage des adresses mémoires. On vous a proposé d’utiliser les
formats %u
et %x
mais ces formats ne sont pas tout à faits correct : %u
permet d’afficher un entier
non signé (unsigned int
) et %x
affiche le nombre non signé en hexadécimal.
Pour vous aider à déboguer, le C considère désormais que les adresses s’affichent avec %p
(entier au
format hexadécimal) et encore, il faut caster les pointeurs en void *
.
Passage par valeur ou par adresse de paramètres d'une fonction
- Écrire une fonction
echangeParValeur()
qui "échange" deux variablesa
etb
entières et que l'on passe par valeur. Dans le corps de la fonction, les valeurs sont affichées avant et après l'échange.
Rappel : le passage par valeur est le passage par défaut en langage C, on travaille sur des copies de variables.
- Écrire un programme principal qui teste cette fonction en
affichant les valeurs de 2 variables entières
i
etj
(déclarées localement dans lemain()
) avant et après l'appel à la fonctionechangeParValeur()
. - Ajouter une fonction
echangeParAdresse()
qui échange vraiment les deux variablesa
etb
entières. Pour ce faire, on fait un passage par adresse. Dans le corps de la fonction, les valeurs sont également affichées avant et après l'échange. Dans le programme principal, on ajoute un affichage avant et après l’échange des valeurs des 2 variables entièresi
etj
par la fonctionechangeParAdresse()
.
Tableaux, pointeurs et chaînes de caractères
Manipulation de pointeurs
S'amuser avec le code suivant :
int main()
{
int tab[] = {0,1,2,3,4,5};
printf("%lu %lu %lu\n", sizeof(char), sizeof(int), sizeof(double));
int * p1;
char * p2;
p1 = tab;
++p1;
printf("%d ", *p1);
p2 = (char *) p1;
p2 += sizeof(int);
printf("%d", *((int*)p2));
printf("%d", *(p1+6));
p1 = NULL;
printf("%d", *p1);
return 0;
}
Le code produit un segmentation fault
comme annoncé en cours ! Il suffit de mettre cette partie coupable en commentaire pour que le reste marche.
Chaines de caractères
Comparer les fonctions suivantes :
int compter1(char * chaine)
{
int i = 0;
while (*(chaine+i) != '\0')
++i;
return i;
}
int compter2(char * chaine)
{
char * s = chaine;
while (*chaine != '\0')
++chaine;
return chaine - s;
}
int compter3(char * chaine)
{
char * s = chaine;
while (*chaine++);
return chaine - s;
}
- Modifier
compter3()
pour incrémenters
et non pluschaine
- Implémenter et tester
strcmp()
en utilisant des pointeurs en paramètres et pas des tableaux.
int main() {
char * s1 = "loic";
// *s1 = 'L'; // 1
printf("%s", s1);
printf("%c", *s1);
printf("%s", s1+2); // 2
// printf("%s", ++s1); // 3
char s2[] = "isima";
s2[0] = 'I';
printf("%s", s2);
// printf("%s", ++s2); // 4
return 0;
}
- Compiler et exécuter le code ci-dessus.
- Que se passe-t-il pour la ligne commentée "2" ?
- Décommenter "1". Que se passe-t-il ?
- Décommenter "4". Qu'obtient-on ?
Révision de la manipulation des outils
Nous allons tester et corriger le programme suivant :
#include<stdio.h>
void saisir(char * s)
{
printf("Saisir une chaine\n");
scanf("%s", s);
}
int main()
{
char *s;
printf("Entrer votre prenom. ");
saisir(s);
printf("Bonjour %s!\n", s);
if (s=="ddd") printf("bizarre \n");
return 0;
}
Compilez le programme et exécutez-le. Deux problèmes sont à résoudre.
Un fichier core ou coredump peut être généré. Il contient alors une image partielle de la mémoire qui peut être utile au débogage.
Je suis sûr que vous pouvez trouver directement ce qui ne va pas avec ce programme mais nous allons utiliser
la surcouche ddd
du débogueur gdb
. Pour ajouter des informations dans le code à analyser, il
faut compiler avec l'option -g
.
$ ddd nom_executable_a_tester &
L'interface graphique de débogueur se lance alors :
- Posez un point d'arrêt sur la première instruction du programme principal en double cliquant
- Exécutez le programme ligne par ligne jusqu'à trouver l'erreur.
- Une fois le premier problème réglé, bien vérifer ce qui se passe lorsque la saisie est "ddd".
Dans le support de cours, vous trouverez les commandes pour utiliser le débogueur en version texte.
L'erreur est encore plus rapide à trouver avec valgrind.
Explication pour le premier bug :>
La variable s
n'est pas explicitement initialisée par le programme. Suivant les compilateurs, cela peut générer un segmentation fault (dans le cas où s
est non nulle) ou plus vicieux, le scanf()
ne lit pas de chaine si s
est initialisée à NULL
. Dans tous les cas, valgrind nous aide en déterminant que s
n'est pas initialisée et que ce n'est pas normal.
Explication pour le deuxième bug :>
En ce qui concerne le test, cela ne peut pas marcher car le programme compare des adresses de chaines de caractères. Il faut utiliser strcmp()
!
Allocation dynamique
- Écrire un programme qui déclare un pointeur
tab
sur des réels flottants double précision, - puis qui alloue dynamiquement 1000000 éléments. L'adresse du tableau sera stockée dans
tab
. - Initialiser ce tableau dynamique avec les carrés des indices de boucle.
- Afficher le tableau
Cela marche ? Super ! mais en fait, on a oublié quelque chose !!! Compilez avec l'option -g
et lancez valgrind :
$ valgrind nom_executable_a_tester
Dans les deux premiers TPs, vous avez utilisé valgrind pour trouver des erreurs de contexte comme des variables non initialisées. Nous allons découvrir maintenant un deuxième usage de valgrind. Valgrind compte le nombre d'allocations mémoire (malloc()
et consœurs) et le nombre de rendus mémoire .... mais quel rendu mémoire ? Celui fait par free()
bien sûr !
Comparer les deux sorties écran de valgrind, avec et sans rendu mémoire. Vous ne devriez pas avoir d'erreur de contexte.