Correction 3 : ES sur fichier

Publié le par JJ

Le code n'était pas faux. On fait des entrée sortie avec des instructions bas niveau, ce que je déconseille en temps normal (fopen et fprintf marchent aussi bien) parce que parfois read/write causent des comportements imprévisibles avec certaines autres fonctions. voir ici[fr]   je cite la section bug de fgets:

 Il est fortement déconseillé de mélanger les appels aux
fonctions de lecture de la bibliothèque
stdio avec les
appels aux fonctions de lecture bas-niveau
read() sur le
descripteur de fichier associé au flux. Les résultats sont
indéfinis, et très probablement indésirables
.

On verra plus tard comment chronométrer le code, mais pour l'instant une simple exécution avec un mp3 d'une chanson formaté (3 à 5 Mo) est assez lent: c'est un gros problème: on aime les choses interactives! (et en plus on n'a pas l'habitude d'attendre 5 secondes pour une simple copie)

 Je donne rendez vous ici [fr]  pour à ceux qui vont vite, sinon lisez le reste de la page  ;)

La copie est une action qui a une durée proportionnelle à la taille d'un fichier mais on peut améliorer le coefficient de proportionnalité! la complexité est en o(n) mais le coefficient peut être réduit: d'ailleurs quand on change de PC, c'est pour une machine 2 fois plus rapide par exemple, pas pour un nouveau jeu d'instruction du processeur ! (ex ancien MMX sur pour les pentiums...)
.

Dans notre programme on lit les octets du fichier source un par un et on les écrit un par un. Si on accepte que l'appel à read() et write() sont coûteux (accès sur le disque...) il faut réduire le nombre d'appel, et donc de lire les octets par blocs.

Quel est la taille du bloc optimal? Une méthode, pas forcement portable,  est d'utiliser stat() [fr]. La structure struct stat possède un champ st_blksize qui me donne la taille en octets du bloc.

Comme j'ai 2 fichiers qui peuvent être sur des support différents (pas dans cette version mais dans l'absolu...) peut être que la taille de block pour les deux fichiers source/cible est différente. Le plus simple est de garder la plus petite taille de block.

Il ne me reste plus qu'a

- créer un tampon de la bonne taille ( avec malloc par exemple )
puis itérer:
- lire taille octets sur le fichier source,
- recopier taille octets lus sur le fichier cible,
enfin
- libérer le tampon (avec free par exemple ).

attention, ce n'est pas parce qu'on demande à read() de lire 4000 octets que read lira 4000 octets. il en lira retour = read(...)
qui varie de -1 (erreur) à taille.

On obtient finalement le code suivant: en gras les changements


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>   
#include <unistd.h>

int main( /*int argc, char** argv*/ ){
   
    int source, cible, retour, taille;
    /* char c; n'est plus utile  */
    char* pt;
    /* pour stoquer les statistiques sur les fichiers */
    struct stat sts_source;
    struct stat sts_cible;   
       
    /* ouverture du fichier qu'on va copier en lecture seule */
    source = open("fichier.mp3", O_RDONLY );
   
    if (source < 0 ){
        perror("erreur à l'ouverture du fichier source\n");
        return -1;
    }       
    /* calcul des stats pour le fichier source*/
    fstat(source, &sts_source);
    printf("Le ficher source a une taille de bloc de  %lu\n",sts_source.st_blksize);
   
   
    /* creation (et non réouverture) d'un fichier en lecture seule,
       avec les droits 700 RWX pour tout le createur */   
    cible = open("fichier_sortie.mp3", O_CREAT | O_RDWR | O_EXCL , S_IRWXU   );
   
    if (cible < 0 ){
        perror("erreur à la creation du fichier cible\n");
        return -1;
    }
    /* calcul des stats pour le fichier cible*/
    fstat(cible, &sts_cible);
   
  printf("Le ficher cible  a une taille de bloc de %lu \n",  sts_cible.st_blksize);
   
   
    /* selection du minimum de la taille des blocs revient à une fonction min() */
   taille=(sts_cible.st_blksize<sts_source.st_blksize)?
     (int)sts_cible.st_blksize :(int) sts_source.st_blksize;

    printf("La taille du block retenue est %d\n", taille);
   
   
    /* creation du tampon de stockage  de taille = taille * sizeof (char ) */
    pt = malloc ( taille );
    if (pt == NULL ){
        perror("malloc fail");
        return -1;       
    }
       
    /*<<<<<<<< lecture du fichier >>>>>>>>*/
    while (1){
        /*----------- lecture d'un caractere -----------*/
        retour = read(source, pt, taille );
       
        /*condition d'arret*/       
        if ( retour < 1) {
            perror("read"); /* c'est aussi lui qui ecrit success à la fin */
            break;
        }
       
        /*----------- ecriture du caractere -----------*/
        retour = write(cible, pt, retour ); /*ici celà serait tres faux d'écrire taille caracteres */
       
        /*condition d'arret*/   
        if ( retour < 1) {
            perror("write");
            break;
        }
       
    }
    /*<<<<<<<< fini ! >>>>>>>>*/
    printf("fin \n");
    close(source);
    close(cible);
    free(pt);
   
    printf("bye \n");
   
    return 0;
   
}

et voilà ;) maintenant sur mon systeme la copie est presque instantannée.

Il y a un autre bug. Ce programme ne permet pas d'assurer le respect des droits d'auteurs et des droits voisins dans la sociétés d'information. Pire on pourait facilement l'utiliser pour copier un DVD, du genre copie privée ou copie de sauvegarde mais ce genre de truc va être interdit par la loi.

Mais moi je n'y suis pour rien, je code. C'est de la faute à M Vanneste et à M Donnedieu de Vabres, hommes ratachés à la famille UMP si un jour on vous oblige à rajouter une "brique" que vous ne controlerez pas et qui s'appelle DRM. (et qui sera mal écrite :)

Bonne journée.

Publié dans Corrections

Commenter cet article

gégé 23/03/2006 19:25

Ton code ça marche pas sous microsoft visual C++ 6 ...

JJ 12/03/2008 23:01

exact.Il est même probable que je m'en moque complètement: VB6 est loin d'être un compilateur moderne. Si on veut faire progresser l'informatique il faut se contraindre à utiliser des outils modernes chaque fois que c'est possible.je dirai même que le code est écrit sous linux alors le résultat sous windows.... sous MacOS X ça marche aussi. étrange!