Skip to content

TP4 bis

Après avoir pris connaissance de la correction de la version 1, terminez le TP.

Correction version 1

tab.h
#ifndef _TAB_H
#define _TAB_H

int str_comp(char *, char *);
#define _GNU_SOURCE /* enleve le warning pour getline */
int analyze_line(char * line, char * searched_name, float * note);
int read_score(float * tab, int * coef, int size, int * count, char * searched_name, char * filename);
float compute_average(float* notes, int* coef, int size);

#endif
tab.c
#include "tab.h"

#include <stdio.h>
#include <stdlib.h>

/* on aurait pu utiliser str_cmp de la bibliotheque string.h, mais le code suivant permet de comprendre ce qui est fait */
int str_comp(char * s1, char * s2)
{
/*
Algo : on parcourt les chaines soit jusqu'à la fin de la plus courte, soit
jusqu'à trouvé un caractère différent.  Si les deux chaines sont les mêmes, on
sort quand on arrive a la fin de s1 et le dernier caractere de s1 etant le meme
que celui de s2 la différence
fait 0
*/
    for(; *s1!='\0' && *s2!='\0' ; s1++,s2++)
        if(*s1!=*s2)break;
    return *s1-*s2;

    /* equivaut à :
       int i;
       for(i=0 ; s1[i]!='\0' && s2[i] != '\0' ; i++)
       if(s1[i] != s2[i]) break;
       return s1[i] - s2[i]; 
       */

}

int analyze_line(char * line, char * searched_name, float * note)
{
    char* found_name;

    if(sscanf(line," %ms %f",&found_name,note) != 2){
        fprintf(stderr,"fichier mal formé\n");
        return 0;
    }

    if(str_comp(searched_name, found_name)==0)
    {
        free(found_name);
        return 1;
    }

    free(found_name); /* a ne pas oublier quand on en a plus besoin */
    return 0;

}

int read_score(float * tab, int * coef, int size, int * count, char * searched_name, char * filename)
{
    int nread;
    char * line = NULL; /* necessaire pour que getline() appelle malloc !! */
    size_t len;
    FILE* desc = fopen(filename,"r"); 
    if(desc==NULL){ 
        /* problème :   impossible d'ouvrir ce fichier */
        fprintf(stderr, "Impossible d'ouvrir le fichier\n") ;
        return 0;
    }
    nread = getline(&line, &len, desc);
    if(nread!=-1)
    {
        if(sscanf(line,"%d",&coef[*count])!=1)
        {
            fprintf(stderr,"le fichier ne commence pas par le coefficient\n");
            free(line);
            fclose(desc);
            return 0;
        }
    }

    while ((nread = getline(&line, &len, desc)) != -1) {
        /* printf("Retrieved line <%s> of length %d:\n",line, nread); */
        float note;
        if(analyze_line(line, searched_name, &note))
        {
            free(line); /* !!!  */
            fclose(desc);
            if(*count<size){
                tab[*count] = note;
                *count = (*count) +1;
                return 1;
            }
            fprintf(stderr, "Nombre de notes max atteind\n") ;
            return 0;
        }
    }
    free(line); /* A ne faire qu'après avoir terminé d'utiliser getline()*/
    fclose(desc);

    fprintf(stderr, "Eleve non trouvé dans le fichier\n") ;
    return 0;

}

float compute_average(float* notes, int* coef, int size)
{
    float avg = 0;
    int i;
    int sum = 0;
    for(i=0;i<size;i++){
        avg+=coef[i]*notes[i];
        sum+=coef[i];
    }
    return avg/sum;
}
test.c
#include "tab.h"

#define MAX 100

#include <stdio.h>

int main(int argc, char* argv[])
{
    float notes[MAX];
    int coef[MAX];

    int count = 0;

    int i;

    char * name = argv[1];

    if(argc<3)
        fprintf(stderr,"Usage : ./prog Nom fichier1.txt [fichier 2.txt ...]\n");

    for(i=2;i<argc;i++)
        if(read_score(notes, coef, MAX, &count, name, argv[i])==0)
            fprintf(stderr,"Fichier <%s> non pris en compte\n",argv[i]);

    if(count>0)
        printf("Moyenne de %s %f\n",name, compute_average(notes,coef,count));


    return 0;
}
makefile
all : prog

test.o : test.c tab.h
    gcc -c -Wall -ansi -pedantic test.c

tab.o : tab.c tab.h
    gcc -c -Wall -ansi -pedantic tab.c

prog : tab.o test.o
    gcc tab.o test.o -o prog

Version 2 : tableau alloué dynamiquement

Le point faible de notre programme est qu'il utilise des tableaux de taille 100 en mémoire, alors que le nombre de matières est inconnu. Pour pouvoir utiliser un tableau de la bonne taille, il faut pouvoir l'allouer dynamiquement, c'est à dire que sa taille sera connue uniqument lors de l'exécution (et pas par le compilateur), et que les données seront stoquée sur le tas et non sur la pile.

Pour obtenir une zone de mémoire utilisable sur le tas, il faut utiliser la fonction malloc. Cette dernière prend en paramètre un entier : la taille en octets de la zone mémoire désirée.

La fonction malloc renvoie un void *, un type qui est compatible avec tous les autres types de pointeurs : c'est bien pratique, ca permet d'avoir une fonction unique pour allouer ce qu'on veut !

Warning

Si il n'y a pas assez de mémoire disponible, malloc renverra la valeur spéciale NULL. Il faut donc toujours vérifier que malloc ne nous à pas renvoyer NULL avant d'utiliser le pointeur retourné !

Si nous revenons à notre tableau de 100 float : quelle est la taille en octets de ce tableau ?

Example

100 fois la taille d'un float ! Mais quelle est la taille d'un float ? Vous n'avez pas à la savoir ! c'est le but de l'opérateur sizeof() dont l'utilisation ressemble à l'appel d'une fonction, sauf qu'on lui donne en paramètre un type.

int taille = sizeof(float);

Voici un code complet qui permet d'allouer un tableau de 23 entiers sur le tas :

int * tab = (int*) malloc(23*sizeof(int));
if(tab==NULL){
    fprintf(stderr, "Erreur d'allocation\n");
    exit(1);
}

Durée de l'allocation ?

Contrairement à une allocation sur la pile, qui est valable jusqu'à la fin de la fonction, l'allocation dynamique n'a pas de fin implicite. La mémoire reste réservée jusqu'à une libération explicite, grace à la fonction free(). Il ne faut pas oublier de libérer la mémoire quand on ne l'utilise plus, sous peine de dégrader les performences, et dans les cas extrèmes de provoquer une fin prématuée du programme si celui ci demande de la mémoire et qu'il n'y en a plus de disponible.

  • Soit le code suivant, expliquez pourquoi il ne peut pas être correct (2 raisons) :

    int* alloue_tableau(int taille)
    {
        int tab[taille];
        return tab;
    }
    

  • Que faut il faire alors ? (réponse : utiliser malloc())

  • Adaptez le programme de la première partie pour que les tableaux soient alloués dynamiquement avec comme taille la valeur argc-2 (pourquoi ?). Vous ferez l'allocation dans une fonction séparée allocate_array() et la libération grace à une fonction free_array()

Version 3 : avec une structure

  • Définissez une structure contenant les deux pointeurs de float (les tableaux) et la valeur count.
  • Faites une fonction d'allocation pour cette structure (sizeof() fonctionne aussi avec un type structuré)
  • modifiez votre programme pour utiliser cette structure plutot que les trois variables séparémment

Pointeurs dans une structure

Si une structure contient un champs a de type int * (par exemple), si on dispose d'une variable v du type de la structure, pour accéder à son champs, il faut écrire :

v.a;
Si on veut accéder à la valeure entière pointée par a, il faut donc écrire :
*(v.a);
Les parenthèses sont nécessaires, sinon on ne pourrait pas savoir si l'opérateur s'applique à v ou à v.a. Afin de lever cette ambiguité sans mettre de parenthèses, on peut également réécrire avec la syntaxe :
v->a;