2010-02-11 4 views
2

Je suis nouveau à C. (J'utilise C89 sur Visual Studio 2010.) J'ai écrit un petit programme qui utilise un arbre pour donner un histogramme des arguments présentés au programme. Y a-t-il des erreurs évidentes de débutant que j'ai faites? Tout semble fonctionner correctement dans mes tests extrêmement limités.C89: Des erreurs débutant dans ce code?

hist.c

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

int main(int argc, char *argv[]) { 
    unsigned int i; 
    struct tree *tree; 
    tree = NULL; 

    for (i = 1; i < argc; i++) { 
     tree = tree_add(tree, argv[i]); // adds strings to tree 
    } 

    tree_dump(tree); // prints results 
    tree_free(tree); // frees memory 
    getchar(); 

    return 0; 
} 

tree.h

struct tree { 
    struct tree *left; 
    struct tree *right; 
    char *value; 
    unsigned count; 
}; 

struct tree *tree_add(struct tree *tree, char *value); 
struct tree *new_tree(); 
void tree_dump(struct tree *tree); 
void tree_free(struct tree *tree); 

tree_free.c

#include "tree.h" 
#include <stdlib.h> 

void tree_free(struct tree *tree) { 
    if (tree->left != NULL) { 
     tree_free(tree->left); 
    } 
    if (tree->right != NULL) { 
     tree_free(tree->right); 
    } 
    free(tree); 
} 

tree_dump.c

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

void tree_dump(struct tree *tree) { 
    if (tree != NULL) { 
     tree_dump(tree->left); 
     printf("%4u\t%s\n", tree->count, tree->value); 
     tree_dump(tree->right); 
    } 
} 

tree_add.c

#include "tree.h" 
#include <stdlib.h> 
#include <string.h> 

struct tree *tree_add(struct tree *tree, char *value) { 
    if (tree == NULL) { 
     tree = new_tree(); 
     tree->value = value; 
     tree->count = 1; 
    } 
    else if (strcmp(tree->value, value) == 0) { 
     tree->count++; 
    } 
    else if (strcmp(value, tree->value) < 0) { 
     tree->left = tree_add(tree->left, value); 
    } 
    else if (strcmp(value, tree->value) > 0) { 
     tree->right = tree_add(tree->right, value); 
    } 
    return tree; 
} 

struct tree *new_tree() { 
    struct tree * tree; 
    tree = malloc(sizeof *tree); 
    tree->left = NULL; 
    tree->right = NULL; 
    tree->value = NULL; 
    tree->count = 0; 
    return tree; 
} 

Répondre

4

Vous pouvez améliorer tree_add.c en stockant le résultat de l'appel de strcmp() dans une variable locale afin de ne pas avoir à l'appeler trois fois. En outre, si ce n'est pas == 0 et que ce n'est pas < 0, cela doit être> 0.

Vous ne vérifiez pas que malloc() renvoie NULL dans new_tree. Pour un programme simple comme celui-ci, vous pouvez simplement quitter avec un message d'erreur "allocation de mémoire échouée", ou renvoyer NULL à l'appelant de new_tree() (sans définir les membres left/right/value/count) et laisser l'appelant gérer Erreur.

Dans l'ensemble, cela ressemble à une bonne implémentation.

+0

La plupart des systèmes d'exploitation, malloc n'échouera jamais, car le système d'exploitation overcommit. malloc vous renverra un peu d'espace d'adressage, mais pas de RAM. Lorsque vous le touchez, le système d'exploitation tentera d'allouer, et s'il ne le peut pas, il prendra une action drastique, vous envoyant éventuellement un SIGKILL par exemple. – smcameron

+0

@smcameron: merci, je n'étais pas au courant de ça. Je passe le plus clair de mon temps à faire du développement embarqué - des plates-formes sans VM et souvent sans malloc (la fragmentation en tas est un vrai problème sur les systèmes qui fonctionnent 24/7 pendant des années sans redémarrer). – tomlogic

+0

@smcameron Sur HP-UX, il est facile de limiter la quantité de mémoire qu'un tas peut recevoir. En conséquence, il est vraiment facile d'obtenir 0 à partir de 'malloc()'. –

-2

une petite erreur: fonte de la valeur de retour de malloc au pointeur de type correct.

Je n'ai pas fait une vérification complète de votre code. Si la sortie est correcte, cela fonctionne probablement. Je ne vois pas de défauts majeurs.

Quelques conseils:

  • un coup d'oeil à C++. L'écriture d'une classe C++ n'est pas si difficile et rendra votre code plus facile à comprendre (une fois que vous serez habitué au C++)
  • Ecrivez un test unitaire pour tester l'exactitude de votre application. Testez quelques cas évidents et non évidents.
+0

"Convertir la valeur de retour de malloc au type de pointeur correct" - en C89 certainement * pas * requis, et je pourrais aller aussi loin que de dire * non recommandé *: http://stackoverflow.com/questions/ 605845/fais-je-jette-le-résultat-de-malloc –

+0

Je ne le savais pas, Mark. Quelque chose appris aujourd'hui. – Patrick

+0

Selon Wikipédia: Omettre la distribution, cependant, crée une incompatibilité avec C++, ce qui l'exige. Par conséquent, l'ajout de la conversion peut toujours être une bonne idée, si vous envisagez d'aller en C++ plus tard. – Patrick

0

Je ne vois aucune erreur évidente. Le code a l'air propre et est facile à lire, ce qui est toujours un bon signe. Un problème pour une application de production est que des vérifications des échecs d'allocation seraient nécessaires, mais vous êtes probablement au courant de cela.

4

Je vois une chose qui ne va pas: vous avez scindé inutilement la mise en œuvre en plusieurs fichiers source C. Ce n'est pas une base de code géante - ce sera plus facile pour vous et pour les futurs responsables si vous mettez toute l'implémentation dans un seul fichier source appelé tree.c.

+0

+1, il n'y a vraiment pas besoin de diviser les fonctions dans plusieurs fichiers pour un programme aussi simple. En général, une meilleure approche serait probablement d'organiser le code en modules comprenant chacun deux fichiers (.h pour l'interface publique et .c pour la mise en œuvre). Vous utiliseriez tree.h et tree.c dans l'exemple de l'OP. – Ree

1

Quelques suggestions mineures:

  • vous devez ajouter un inclusion guard à votre fichier d'en-tête

  • un qualificatif const doit être ajouté au membre value que la modification peut annuler l'ordre de l'arbre

  • sur Windows, je préfère system("pause") sur getchar() pour garder la fenêtre de la console ouverte; sur * nix, ne le font pas du tout

et une petite note sur le style de codage: à mon avis,

if(tree) { ... } 
if(!tree) { ... } 

est plus lisible (et « dans l'esprit » de C - rappelez-vous, une valeur scalaire peut être utilisé dans des contextes booléens) que

if(tree != NULL) { ... } 
if(tree == NULL) { ... } 

d'autres personnes pourraient être en désaccord!.

+0

+1 pour le protecteur d'inclusion. Je ne savais pas à ce sujet. –

0
struct tree *new_tree() { 
    struct tree * tree; 
    tree = malloc(sizeof *tree); 

A une erreur.

malloc(sizeof(tree)); est ce que vous voulez. Dans votre code, vous allouez la taille d'un 'pointeur'. Pas la structure.

modifier: Je me trompe. Garder cette réponse en vie pour référence future par moi-même et les autres.

+0

sizeof * tree est correct ici - il donne la taille de la structure. sizeof tree donne la taille du pointeur. Avoir la structure et le pointeur ont le même nom n'est PAS une bonne pratique cependant. N'oubliez pas que les noms de structure et les noms de variable se trouvent dans des espaces de noms différents. Sans le qualificateur «struct», nous sommes dans l'espace de noms des variables. –

+0

@Paul: vous vous trompez; Le code de Rosarch est réellement recommandé pour éviter de dupliquer les informations de type – Christoph

+0

Guh. Je n'ai pas compris cela - je ne savais pas que struct et noms de variables avaient des espaces de noms différents. –

1

Je ne vois pas immédiatement d'erreurs dans le code qui affecterait sa fonctionnalité ... Seulement quelques trucs stylistiques. (1) C89 n'est pas très favorable aux déclarations avec initialiseur, mais dans de nombreux cas c'est parfaitement possible. Ainsi, par exemple, au lieu de

int main(int argc, char *argv[]) { 
    unsigned int i; 
    struct tree *tree; 
    tree = NULL; 
    ... 

J'écrire

int main(int argc, char *argv[]) { 
    unsigned int i; 
    struct tree *tree = NULL; 
    ... 

(2) Je ne suis pas un grand fan des optimisations prématurées, mais néanmoins plusieurs appels consécutifs à strcmp avec les mêmes arguments sont trop pour moi. En tree_add je ferais

int cmp = strcmp(tree->value, value); 

une seule fois, puis branche sur la valeur de cmp.

(3) Une question de style très personnel, mais lorsque vous travaillez avec des valeurs avec une sémantique essentiellement identique, une affectation chaînée peut réellement améliorer la lisibilité du code. Dans votre cas

tree->left = NULL;    
tree->right = NULL; 

pourrait mieux sous la forme

tree->left = tree->right = NULL; 

Mais encore une fois, cela est facilement abusé. Et encore une fois, c'est une question de style personnel.

(4) Votre arbre est-il destiné à fournir un modifiable chemin d'accès aux valeurs de chaîne stockées dans celui-ci? Si ce n'est pas le cas (et les structures de données ordonnées ne le font généralement pas), alors utiliser const char * partout où vous avez actuellement un char * pourrait être une meilleure idée (dans struct tree ainsi).

(5) Comme d'autres déjà noté, il n'y a généralement aucune raison de diviser un module en autant de petits fichiers d'implémentation.

Quoi qu'il en soit, votre code est superbe. Je donnerais à votre code des notes élevées pour tree = malloc(sizeof *tree) (expression cible sous sizeof au lieu de type), pour if (tree != NULL) (au lieu de if (!tree)), pour utiliser un type non signé pour représenter une valeur non signée, pour faire un effort pour initialiser correctement un objet au lieu de simplement frapper avec memset ou calloc.

Questions connexes