tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
ISIMA
  • twitter
  • linkedin
  • viadeo

[C] TP 2 : Pointeurs et outils

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 :

Attention, 2, "2" et '2' sont des choses complètement différentes !

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

  1. Écrire une fonction echangeParValeur() qui "échange" deux variables a et b 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.

  1. Écrire un programme principal qui teste cette fonction en affichant les valeurs de 2 variables entières i et j (déclarées localement dans le main()) avant et après l'appel à la fonction echangeParValeur().
  2. Ajouter une fonction echangeParAdresse() qui échange vraiment les deux variables a et b 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ères i et j par la fonction echangeParAdresse().

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

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

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 :

fenetre de ddd
  1. Posez un point d'arrêt sur la première instruction du programme principal en double cliquant
  2. Exécutez le programme ligne par ligne jusqu'à trouver l'erreur.
  3. 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

  1. Écrire un programme qui déclare un pointeur tab sur des réels flottants double précision,
  2. puis qui alloue dynamiquement 1000000 éléments. L'adresse du tableau sera stockée dans tab.
  3. Initialiser ce tableau dynamique avec les carrés des indices de boucle.
  4. 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.