Exercices INGINIOUS

Deux sortes d’exercices INGINIOUS vous sont proposés durant cette semaine. Les premiers portent sur les structures chaînées car ces structures de données permettent de bien vérifier la compréhension des pointeurs en C.

Après avoir écrit de nombreuses fonctions C, il est maintenant temps pour vous de commencer à écrire des programmes composés de plusieurs fonctions. Pour cela, l’utilitaire make(1) vous sera très utile. Prenez un peu de temps pour lire le chapitre qui lui est consacré dans le syllabus et essayez de répondre aux questions ci-dessous :

Lorsque l’on écrit des programmes en C ou dans un autre langage, il est important de tester le bon fonctionnement de toutes les fonctions du programme pour éviter des erreurs et autres bugs difficiles à corriger. L’idéal est de commencer par écrire les tests qui valident le bon fonctionnement de chaque fonction avant d’écrire cette fonction. Plusieurs librairies peuvent vous aider à écrire de tels tests. CUnit (CUnit: librairie de tests) est l’un d’entre elles. Prenez le temps de lire le chapitre qui lui est consacré dans le syllabus.

Pour démontrer votre bon utilisation de make(1) et CUnit, reprenez le programme que vous avez écrit pour l’exercice test , divisez-le en plusieurs fichiers, ajoutez-y des tests unitaires pour chaque fonction et utilisez make(1) pour automatiser le tout. Si vous voulez allez plus loin, essayez d’utiliser la librarie getopt(3) pour traiter les arguments reçus en ligne de commande.

Verifiez vos réponses

Exercices

  1. Le principe de localité est un principe très important pour comprendre les performances de programmes qui accèdent beaucoup à la mémoire. Considérons tout d’abord un programme qui doit initialiser une grande zone mémoire obtenue via malloc(3).

    • Ecrivez en C une fonction d’initialisation de cette zone mémoire à la valeur 1252 qui profite de la localité spatiale
    • Ecrivez en C une fonction d’initialisation de cette zone mémoire qui ne profite pas du tout de la localité spatiale
    • Comparez les performances des deux programmes que vous avez écrit. Si nécessaire, désactivez l’optimisation du compilateur.
  2. Un programmeur doit manipuler des tableaux contenant 100.000.000 éléments. Chaque élément du tableau contient un nombre réel et une chaîne contenant 40 caractères. Une opération très courante à effectuer est de calculer la somme de tous les éléments du tableau. A votre avis, quelles seront les performances des deux implémentations suivantes de ce programme :

    • Le tableau est implémenté comme un tableau contenant 100.000.000 structures avec dans chaque structure un float et un char c[40].
    • Le tableau est implémenté comme deux tableaux distincts. Le premier contient tous les float et le second toutes les chaînes de caractères.
  3. Un programmeur propose deux fonctions différentes pour calculer la somme des éléments d’un tableau à deux dimensions. Intégrez ces fonctions dans un programme afin d’en mesurer les performances avec gettimeofday(2). Quelle est la variante la plus rapide et pourquoi ?

    #define SIZE 10000
    
    int matrix[SIZE][SIZE];
    
    int sum() {
      int sum=0;
      for(int i=0;i<SIZE;i++) {
        for(int j=0;j<SIZE;j++) {
          sum+=matrix[i][j];
        }
      }
      return sum;
    }
    
    int sum2() {
      int sum=0;
      for(int i=0;i<SIZE;i++) {
        for(int j=0;j<SIZE;j++) {
          sum+=matrix[j][i];
        }
      }
      return sum;
    }
    

Question de bilan final

Considérez le programme suivant.

#include <stdlib.h>

int cours = 1252;

int f(int *a, int b) {
	int c = 97850**a + b;
	int x = 2052;
	*a = x * c;
	return c;
}

int main(int argc, char *argv[]) {
	int d = 42;
	char *tab = malloc(20 * sizeof(char));
	int *res = (int *) tab;
	*tab = f(&cours, d);
	return 0;
}