Se connecter

Informatique

Programmation

Sujet : [C] Des critiques à faire sur mon code ?
1
[hard]ware
Niveau 14
27 octobre 2018 à 18:11:40

Salut :ok:

Alors voici le sujet :
https://www.noelshack.com/2018-43-6-1540656209-gkqcmno2.png

A savoir que c'est un cours sur les Systèmes d'Exploitations, avec la norme POSIX.
L'intérêt est aussi d'apprendre à utiliser des fonctions système, d'où l'utilisation des open, close, read et write et non de fopen, fclose, fread, fwrite...

Et voici mon code :
https://pastebin.com/D9VCmbrj

Tout compile et tout semble fonctionner :ok:

Je me demandais si vous aviez des critiques à faire sur le manière d'écrire ou sur l'algo de façon générale :(
Notamment sur la reconnaissance des options, qui est un petit peu chaotique :doute:

:merci:

godrik
Niveau 22
27 octobre 2018 à 18:32:24

La fonction est beaucoup trop longue pour etre lisible.

Passe le parsing des parametre de main dans une fonction a part.

Typiquement quand le parsing des parametres echouent, le code affiche un "usage"

Si j'ai bien suivi c'est une fonction de copie de fichier. Tu puex declaer la logique de copie dnas un fichier a part ce qui va rendre le code vachement plus lisible egalement.

Pourquoi est ce que le code decide d'echouer en cas ou le write ne correspond pas a la taille du read? C'est possible que le buffer de sortie soit a moitie plein, et qu'il faille decouper le buffer en plusieurs write. Ca serait probablement mieux de faire ca.

KiraiKiller
Niveau 2
27 octobre 2018 à 19:26:40

Tu devrais séparer les paramètres de ta commande dans une structure spécifiée pour cela et la mettre dans un fichier à part que tu incluras dans ton code ensuite ça sera beaucoup plus organisé et lisible.

Ensuite au niveau de l'algorithme je te conseille d'utiliser une machine à état qui sont très utiles pour ce genre de commande notamment pour voir si elle est correcte et pour la traiter sans mettre des branchements conditionnels partout dans ton code.

Pour cela tu dois d'abord t'être familiarisé avec la théorie des graphes et avoir ton module pour gérer cette structure.

Typiquement tu as une chaîne de caractères contenant ce que ton utilisateur à taper sur laquelle tu fais un prétraitement qui supprime les caractères espaces par exemple.

Ensuite tu analyses chacun caractères de ta nouvelle chaîne et en fonction de ces derniers et de l'appartenance à tel alphabet ou non alors tu passes d'un état A à un état B :

Exemple : Ton programme s'arrête sur : "-" . Il s'attend donc à ce que la prochaine lettre soit une lettre d'un paramètre.
Il analyse la deuxième lettre : deux états possibles :
A = Je quitte le programme car la lettre rencontré est invalide
B = Je continue l'execution et stock le paramètre dans un bool

[hard]ware
Niveau 14
27 octobre 2018 à 20:42:12

Le 27 octobre 2018 à 18:32:24 godrik a écrit :
La fonction est beaucoup trop longue pour etre lisible.

Passe le parsing des parametre de main dans une fonction a part.

Typiquement quand le parsing des parametres echouent, le code affiche un "usage"

comment ça un "usage" ? :( tu veux dire, expliquer comment les paramètres doivent être passés ?

Si j'ai bien suivi c'est une fonction de copie de fichier. Tu puex declaer la logique de copie dnas un fichier a part ce qui va rendre le code vachement plus lisible egalement.

Est-ce utile de faire un fichier à part pour "si peu" ?
A la limite une fonction, ouais :(

Pourquoi est ce que le code decide d'echouer en cas ou le write ne correspond pas a la taille du read? C'est possible que le buffer de sortie soit a moitie plein, et qu'il faille decouper le buffer en plusieurs write. Ca serait probablement mieux de faire ca.

Je demande à read de lire BUFFER_SIZE octets. Suivant la taille du fichier, il pourra lire entre 0 et BUFFER_SIZE octets.
Ensuite quand j'écris, si write n'a pas écrit tous ces octets lus, alors ils ne seront jamais écrits, au prochain tour de boucle ce sera la suite qui sera lue. Donc si write a pas tout écrit... ça va pas...
J'ai faux ?

[hard]ware
Niveau 14
27 octobre 2018 à 20:47:25

Le 27 octobre 2018 à 19:26:40 KiraiKiller a écrit :
Tu devrais séparer les paramètres de ta commande dans une structure spécifiée pour cela et la mettre dans un fichier à part que tu incluras dans ton code ensuite ça sera beaucoup plus organisé et lisible.

Ensuite au niveau de l'algorithme je te conseille d'utiliser une machine à état qui sont très utiles pour ce genre de commande notamment pour voir si elle est correcte et pour la traiter sans mettre des branchements conditionnels partout dans ton code.

Pour cela tu dois d'abord t'être familiarisé avec la théorie des graphes et avoir ton module pour gérer cette structure.

Typiquement tu as une chaîne de caractères contenant ce que ton utilisateur à taper sur laquelle tu fais un prétraitement qui supprime les caractères espaces par exemple.

Ensuite tu analyses chacun caractères de ta nouvelle chaîne et en fonction de ces derniers et de l'appartenance à tel alphabet ou non alors tu passes d'un état A à un état B :

Exemple : Ton programme s'arrête sur : "-" . Il s'attend donc à ce que la prochaine lettre soit une lettre d'un paramètre.
Il analyse la deuxième lettre : deux états possibles :
A = Je quitte le programme car la lettre rencontré est invalide
B = Je continue l'execution et stock le paramètre dans un bool

Carrément un fichier à part pour lire les paramètres ? Une fonction suffirait non ? :(

Ah oui alors les graphes j'en suis vraiment au tout début, du genre "je sais ce qu'est un graphe". Je sais pas encore implanter ça. (j'ai pris du retard, la prof qui enseigne les graphes est tellement naze... :-( )

Et sinon, à part le "-", comment puis-je reconnaitre les caractères et déterminer s'ils sont correct ou non ?
Ne serais-ce que savoir qu'on m'a donné un chiffre ? Par exemple j'utilise la fonction "atol" qui converti une chaine de caractères en long int, mais je fais confiance à l'utilisateur qui est censé me donner un nombre et non un mot. Je ne sais pas vérifier ça :doute:

KiraiKiller
Niveau 2
27 octobre 2018 à 21:13:49

Carrément un fichier à part pour lire les paramètres ? Une fonction suffirait non ? :(

Non je parle d'un fichier header contenant une structure de données type command avec tous les paramètres et la chaîne de caractère que tu traites et que tu passes à chacune des fonctions de ton programme.

Ah oui alors les graphes j'en suis vraiment au tout début, du genre "je sais ce qu'est un graphe". Je sais pas encore implanter ça. (j'ai pris du retard, la prof qui enseigne les graphes est tellement naze... :-( )

Regarde sur Internet tu trouveras des implémentations toutes faites et génériques pour implémenter des graphes ou bien avec les bibiliothèques boostgraph en C++ qui est très performante. Si tu as besoin de revoir la théorie ou que t'as pas compris certaines notions tu as des pdf et cours universitaires très complet et détaillés sur Internet.

Et sinon, à part le "-", comment puis-je reconnaitre les caractères et déterminer s'ils sont correct ou non ?

Ne serais-ce que savoir qu'on m'a donné un chiffre ? Par exemple j'utilise la fonction "atol" qui converti une chaine de caractères en long int, mais je fais confiance à l'utilisateur qui est censé me donner un nombre et non un mot. Je ne sais pas vérifier ça :doute:

C'est typiquement ce à quoi servent les alphabets. Il te suffit simplement de vérifier si ton caractère appartient à cet alphabet pour décider en voir son type. C'est exactement ce que isdigit fait :

#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {

    char buffer[80];
    printf( "Enter your text : " );
    scanf( "%[^\n]", buffer );

    bool isCorrect = true;
    size_t length = strlen( buffer );
    for( int i=0; i<length; i++ ) {
        if ( ! isdigit( buffer[i] ) ) {
            isCorrect = false;
        }
    }

    if ( isCorrect ) {
        printf( "Your text (%s) is a number\n", buffer );
    } else {
        printf( "Your text (%s) is not a number\n", buffer );
    }

    return EXIT_SUCCESS;
}

Voilà une page WIkipédia qui explique globalement ce qu'est une machine à état :
https://fr.wikipedia.org/wiki/Automate_fini

La figure 4 ça devrait t'aider à voir plus clair.

godrik
Niveau 22
27 octobre 2018 à 23:42:14

Le 27 octobre 2018 à 20:42:12 [Hard]Ware a écrit :

Le 27 octobre 2018 à 18:32:24 godrik a écrit :
La fonction est beaucoup trop longue pour etre lisible.

Passe le parsing des parametre de main dans une fonction a part.

Typiquement quand le parsing des parametres echouent, le code affiche un "usage"

comment ça un "usage" ? :( tu veux dire, expliquer comment les paramètres doivent être passés ?

yep.

Si j'ai bien suivi c'est une fonction de copie de fichier. Tu puex declaer la logique de copie dnas un fichier a part ce qui va rendre le code vachement plus lisible egalement.

Est-ce utile de faire un fichier à part pour "si peu" ?
A la limite une fonction, ouais :(

Je voulais dire une fonction en effet.

Pourquoi est ce que le code decide d'echouer en cas ou le write ne correspond pas a la taille du read? C'est possible que le buffer de sortie soit a moitie plein, et qu'il faille decouper le buffer en plusieurs write. Ca serait probablement mieux de faire ca.

Je demande à read de lire BUFFER_SIZE octets. Suivant la taille du fichier, il pourra lire entre 0 et BUFFER_SIZE octets.
Ensuite quand j'écris, si write n'a pas écrit tous ces octets lus, alors ils ne seront jamais écrits, au prochain tour de boucle ce sera la suite qui sera lue. Donc si write a pas tout écrit... ça va pas...
J'ai faux ?

oui. C'est possible que write ne puisse pas tout ecrire, mais qu'un appel prochain arrive a ecrire. Il y a plein de raison qui peuvent faire que write n'ecrive pas tout. Le buffer d'io est plein, le processus a ete interrompu par un signal. Si tu n'arrives pas a tout ecrire, ce n'est pas un cas d'erreur. Les cas d'erreur sont note dans man 2 write. Mais ne pas tout pouvoir ecrire n'est pas forcement une erreur.

godrik
Niveau 22
27 octobre 2018 à 23:48:25

Ensuite au niveau de l'algorithme je te conseille d'utiliser une machine à état qui sont très utiles pour ce genre de commande notamment pour voir si elle est correcte et pour la traiter sans mettre des branchements conditionnels partout dans ton code.

Pour cela tu dois d'abord t'être familiarisé avec la théorie des graphes et avoir ton module pour gérer cette structure.

C'est completement overkill pour parser des parametres aussi simple. C'est enormement de code, de complication, et de source de bug, pour un avantage pas completement clair.
Si tu veux retirer le parsing des parametres, alors autnt utiliser getopt(3), ca sera plus simple.

KiraiKiller
Niveau 2
27 octobre 2018 à 23:59:32

Le 27 octobre 2018 à 23:48:25 godrik a écrit :

Ensuite au niveau de l'algorithme je te conseille d'utiliser une machine à état qui sont très utiles pour ce genre de commande notamment pour voir si elle est correcte et pour la traiter sans mettre des branchements conditionnels partout dans ton code.

Pour cela tu dois d'abord t'être familiarisé avec la théorie des graphes et avoir ton module pour gérer cette structure.

C'est completement overkill pour parser des parametres aussi simple. C'est enormement de code, de complication, et de source de bug, pour un avantage pas completement clair.
Si tu veux retirer le parsing des parametres, alors autnt utiliser getopt(3), ca sera plus simple.

Ah bon je n'étais pas au courant de l'existence d'une fonction qui s'occupe de parser les paramètres. Autant pour moi dans ce cas ça lui sera plus utile d'utiliser cette fonction

godrik
Niveau 22
28 octobre 2018 à 00:04:58

Mouaos ca fait parti de posix.1-2001

[hard]ware
Niveau 14
29 octobre 2018 à 21:20:12

Le 27 octobre 2018 à 23:42:14 godrik a écrit :

Le 27 octobre 2018 à 20:42:12 [Hard]Ware a écrit :

Le 27 octobre 2018 à 18:32:24 godrik a écrit :
La fonction est beaucoup trop longue pour etre lisible.

Passe le parsing des parametre de main dans une fonction a part.

Typiquement quand le parsing des parametres echouent, le code affiche un "usage"

comment ça un "usage" ? :( tu veux dire, expliquer comment les paramètres doivent être passés ?

yep.

Si j'ai bien suivi c'est une fonction de copie de fichier. Tu puex declaer la logique de copie dnas un fichier a part ce qui va rendre le code vachement plus lisible egalement.

Est-ce utile de faire un fichier à part pour "si peu" ?
A la limite une fonction, ouais :(

Je voulais dire une fonction en effet.

Pourquoi est ce que le code decide d'echouer en cas ou le write ne correspond pas a la taille du read? C'est possible que le buffer de sortie soit a moitie plein, et qu'il faille decouper le buffer en plusieurs write. Ca serait probablement mieux de faire ca.

Je demande à read de lire BUFFER_SIZE octets. Suivant la taille du fichier, il pourra lire entre 0 et BUFFER_SIZE octets.
Ensuite quand j'écris, si write n'a pas écrit tous ces octets lus, alors ils ne seront jamais écrits, au prochain tour de boucle ce sera la suite qui sera lue. Donc si write a pas tout écrit... ça va pas...
J'ai faux ?

oui. C'est possible que write ne puisse pas tout ecrire, mais qu'un appel prochain arrive a ecrire. Il y a plein de raison qui peuvent faire que write n'ecrive pas tout. Le buffer d'io est plein, le processus a ete interrompu par un signal. Si tu n'arrives pas a tout ecrire, ce n'est pas un cas d'erreur. Les cas d'erreur sont note dans man 2 write. Mais ne pas tout pouvoir ecrire n'est pas forcement une erreur.

Je comprends pas bien ça.
Ne pas pouvoir écrire n'est pas forcément une erreur... Oui mais, est-ce que sera quand même écrit "un jour" ?
Parce que quelle que soit la raison, même si à la base ce n'est pas une erreur, si ce n'est jamais écrit, alors la copie sera incomplète et je devrais traiter ça comme une erreur ? :(
C'est le genre de truc que je peux pas trop tester en plus :(

godrik
Niveau 22
29 octobre 2018 à 21:48:27

c'est possible que tu essaye de faire un write de 100 octets, et seulement les 20 premiers sont ecrit. Apres tu dois faire le write des 80 octets qui manquent.
Tant que write ne retourne pas une erreur, tu ne sais pas si tu es dans une condition d'erreur.

[hard]ware
Niveau 14
29 octobre 2018 à 23:46:09

Ah oui du coup, je test si write a bien tout écrit, si c'est pas le cas, dans le même tour de boucle je refais un write de ce qu'il reste en fait ?

GouKen91
Niveau 59
01 novembre 2018 à 14:08:20

Variables non initialisées.
Certains includes inutiles.
Retour en plein milieu.
Pas de fonction.
Pas d'utilisation de getopts.
Pas d'accolades ouvrants/fermantes de temps en temps.
Des goto pas justifiés.
Libération de mémoire sans tester le pointeur.
Fermeture de fd sans tester le fd.

J'te reçois en entretien, t'es pas pris. :hap:

Sinon, recompile déjà avec -Wall -Wextra, ça permet de faire un premier nettoyage.

[hard]ware
Niveau 14
01 novembre 2018 à 23:58:06

Le 01 novembre 2018 à 14:08:20 GouKen91 a écrit :
Variables non initialisées.

lesquelles ? :doute:

Certains includes inutiles.

Ah oui, c'est un mauvais copier/coller :(

Retour en plein milieu.

qu'est-ce que ça veut dire ? :doute:

Pas de fonction.

Ouais, je vais au moins mettre la lecture des arguments en fonction.
Je pourrais aussi faire la lecture/écriture j'imagine.

Pas d'utilisation de getopts.

Non, je connaissais pas ça.
Je regarderai pour une prochaine fois.

Pas d'accolades ouvrants/fermantes de temps en temps.

Simplement pour les petits if ? :(
C'est plus une question de style qu'autre chose ça non ? :doute:

Des goto pas justifiés.

On m'a dit que c'était une pratique courante d'utiliser un goto pour ce genre de gestion d'erreur. Pourquoi pas ?
Qu'est-ce qui en justifierais sinon ?

Libération de mémoire sans tester le pointeur.

Le manuel spécifie que faire free(NULL) n'est pas une erreur et que ça fait rien. Tu veux que je vérifie quoi, et que j'en fasse quoi ? Par ailleurs valgrind ne me signale rien...

Fermeture de fd sans tester le fd.

Le fd ne va jamais changer tout seul ? Pourquoi le tester ? Et surtout comment ? Qu'est-ce qui peut se produire ?

J'te reçois en entretien, t'es pas pris. :hap:

Oui ben je passe pas un entretien là, je suis là pour apprendre :(

Sinon, recompile déjà avec -Wall -Wextra, ça permet de faire un premier nettoyage.

Je compile déjà avec :
gcc -std=c11 -Wall -Wconversion -Werror -Wextra -Wpedantic -c "%f" -pthread -fstack-protector-all

GouKen91
Niveau 59
03 novembre 2018 à 22:13:03

c11 :malade:

godrik
Niveau 22
04 novembre 2018 à 07:21:30

Le 03 novembre 2018 à 22:13:03 GouKen91 a écrit :
c11 :malade:

Bah c'est dangeeux de faire du C18. C'est trop recent, il y a plein de compilateur qui ne vont pas le supporter. Mais le C11 est sorti il y a belle lurette maintenant. Tous les compilateurs dignes de ce nom vont avoir le support qui va bien.
Pourquoi se faire chier avec un C antique?

[hard]ware
Niveau 14
04 novembre 2018 à 13:32:24

Le 03 novembre 2018 à 22:13:03 GouKen91 a écrit :
c11 :malade:

Peux-tu t'expliquer ?
Tu me sors des critiques, c'est gentil mais si tu ne t'expliques pas plus que ça, ça me sert pas à grand chose :doute:

Le 04 novembre 2018 à 07:21:30 godrik a écrit :

Le 03 novembre 2018 à 22:13:03 GouKen91 a écrit :
c11 :malade:

Bah c'est dangeeux de faire du C18. C'est trop recent, il y a plein de compilateur qui ne vont pas le supporter. Mais le C11 est sorti il y a belle lurette maintenant. Tous les compilateurs dignes de ce nom vont avoir le support qui va bien.
Pourquoi se faire chier avec un C antique?

Eh bien c'est ce qu'on nous demande à la fac :(
Il y a de grosses différences ?
J'ose espérer que oui, en 7 ans :hap: Mais genre c'est une façon de penser différente ou quoi ?
De nouvelles fonctions, des fonctions supprimées, des fonctions changées...? :(

godrik
Niveau 22
04 novembre 2018 à 17:53:32

Bah c'est dangeeux de faire du C18. C'est trop recent, il y a plein de compilateur qui ne vont pas le supporter. Mais le C11 est sorti il y a belle lurette maintenant. Tous les compilateurs dignes de ce nom vont avoir le support qui va bien.
Pourquoi se faire chier avec un C antique?

Eh bien c'est ce qu'on nous demande à la fac :(
Il y a de grosses différences ?
J'ose espérer que oui, en 7 ans :hap: Mais genre c'est une façon de penser différente ou quoi ?
De nouvelles fonctions, des fonctions supprimées, des fonctions changées...? :(

Nan, C18 est un bugfix de C11. Il y avait quelques problemes de specifications dans certaines additions de C11. C18 corrige les problemes connu aujourd'hui. C'est peu probable que tu rencontres les problemes que C18 corrige.

C est un langage relativement stable. C11 introduisait principalement des changements pour garder C et C++ aligne la ou c'est possible. Ca introduit des fonctionnalites de multithreading (au lieu de laisser la question a une lib OS dependente), de nouveau attribut pour aider l'alignment de donnee (si tu ne sais pas ce que ca veut dire, t'en fait pas).

godrik
Niveau 22
04 novembre 2018 à 17:55:08

Le 04 novembre 2018 à 13:32:24 [Hard]Ware a écrit :

Le 03 novembre 2018 à 22:13:03 GouKen91 a écrit :
c11 :malade:

Peux-tu t'expliquer ?
Tu me sors des critiques, c'est gentil mais si tu ne t'expliques pas plus que ça, ça me sert pas à grand chose :doute:

En effet, ce qu'il dit ne t'aide pas beaucoup. T'en fait pas. Si tu veux plus de commentaire, poste un code a jour apres avoir decoupe le code en deux fonctions.

1
Sujet : [C] Des critiques à faire sur mon code ?
   Retour haut de page
Consulter la version web de cette page