Chaîne de caractères dynamique

a marqué ce sujet comme résolu.

Bonjour à tous !

J'aimerais avoir votre avis sur mon petit tutoriel traitant des chaînes de caractères dynamiques en C. Au niveau de la forme, ce qui est dit, est-ce que le sujet vous intéresse, si vous aimeriez en savoir davantage, etc !

Merci ! Vous pouvez y accéder ici : bêta du tutoriel.

Je compte faire plusieurs petits tutoriels dans le même style, traitant surtout de programmation. :)

+0 -0

Je n'ai pas lu en détail (je préfère l'utilisation de std::string :) ), mais quand même trois remarques :

  • system("pause") n'est pas portable
  • il n'y a pas de gestion des erreurs (calloc, realloc)
  • pourquoi buffer est un pointeur ? (l'intérêt de l'encapsulation est d'éviter la gestion manuelle de la mémoire)
+1 -0

1
2
3
#define EXTRA_LEN 8
/* ... */
unsigned int capacity = totalLen + EXTRA_LEN;

Je trouve ça un peu étrange de rajouter un incrément pour ça. En effet 8, pourquoi 8 ? Cela risque d’être une longueur ridiculement petite dès que tu va jouer avec des morceaux de chaînes de taille réel. Généralement on définit plutot un ratio pour ce genre de chose. Par exemple "doubler la taille systématiquement", etc. Ainsi la capacité résiduelle est du même ordre de grandeur que la chaine.

a- Enlève le system("pause"). 1 c'est limité à windows. 2. exiger la pause ne sert à rien quant on a appris à utiliser son IDE.
De plus, cela n'a rien à faire dans un tuto très ciblé où les gens sont censés déjà avoir pris leur IDE en main.

b- En C99 les #define sont dépréciés en faveur des constantes pour stocker des … constantes.

c- Mais … côté politique intelligente, jouer avec l'extra-length va te compliquer le boulot car tu vas devoir jongler entre capacité et longueur de chaîne. En C++, la politique initiale fut de plus ou moins multiplier par 2 à chaque fois. Ceci dit, je suis tombé sur un article très intéressant au sujet de ce qui aurait été le plus efficace: https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md
Mais tout cela c'est de la technique avancé. Il faudrait aussi envisager les SSO (Small String Optimization), ou juste la version du pauvre : la Empty String Optimization). Dans un premier temps, realloc() fait très bien son boulot, et en interne il va opérer des choses équivalentes vu qu'il ne déplacera la mémoire que si la nouvelle quantité demandée ne rentre plus à l'endroit courant.

d- En C aussi, size_t existe ( http://ewontfix.com/9/ – très bon blog au demeurant)

e- init() a pour précondition que le paramètre str soit non null -> assert est ton ami

f- D'ailleurs, pourquoi allouer buffer ?

g- calloc() n'apporte pas grand chose. Un buffer->s[0] = 0 suffit amplement, c'est d'ailleurs un des deux invariants de ta chaine: buffer->s[buffer->strlen] == 0 ; l'autre est que buffer->strlen <= buffer->capacity ; et tu sembles aussi exiger que capacity soit non nul.

(Pour calloc, Faut dire que je suis très peu réceptif à la programmation défensive, pour ne pas dire hostile)

BTW, on notera que l'emploi de calloc n'est pas cohérent avec celui de realloc.

h- Ta fonction append n'est pas const-correcte, il manque le const.

i- Ton utilisation de reallocest totalement bugguée (ou une des raisons qui fait que je critique le C: le pays magique où les erreurs n'existent pas). Une utilisation correcte est:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int append_raw(str * rhs_, char const* lhs_) {
    // les préconditions
    assert(rhs_);
    assert(lhs_);
    // les invariants -> à mettre dans une fonction car cela servira partout en début en fin
    assert(rhs_->buffer);
    assert(rhs_->buffer[rhs_->strlen] == 0);
    assert(rhs_->strlen <= rhs_->capacity);

    const size_t len = strlen(lhs_);
    if ( .... ) {
        const size_t new_capacity = ...;
        // la post-condition du calcul de capacity qui devient le futur invariant
        assert(new_capacity >= rhs_->buffer + len); 
        char * s = realloc(rhs_->buffer, new_capacity+1);
        if (s == 0) { return ENOMEM; }
        // alloc OK => on commite
        rhs_->buffer = s;
        rhs_->capacity = new_capacity;
    }
    // on peut balancer directement memcpy car les tailles 
    // sont bonnes et on sait d'où on part
    memcpy(rhs_->buffer+rhs_->strlen, lhs_, len);
    rhs_->strlen += len;
    rhs_->buffer[rhs_->strlen] = 0;

    // De nouveau les invariants

    // et on valide que tout s'est bien passé
    return 0;
}

NB: J'ai appelé la fonction append_raw car il manque l'ajout entre deux structures str, ou l'ajout char const* + str*.

j- erase et init ne sont pas équilibrées. Si erase libère le pointeur reçu, alors init doit allouer une structure chaine et la renvoyer. Les responsabilités d'allocation et de libération doivent être équilibrées.

1
2
3
#define EXTRA_LEN 8
/* ... */
unsigned int capacity = totalLen + EXTRA_LEN;

Je trouve ça un peu étrange de rajouter un incrément pour ça. En effet 8, pourquoi 8 ? Cela risque d’être une longueur ridiculement petite dès que tu va jouer avec des morceaux de chaînes de taille réel. Généralement on définit plutot un ratio pour ce genre de chose. Par exemple "doubler la taille systématiquement", etc. Ainsi la capacité résiduelle est du même ordre de grandeur que la chaine.

Kje

Comme je l'ai énoncé dans le tutoriel, ceci est une base que vous pouvez utiliser pour un de vos projet par exemple. Ici, j'ai volontairement pris 8, car c'est ce que l'on appelle un cas d'école (car dans mon exemple, j'ajoute de petites chaînes si tu regardes bien, donc 8 devient pertinent). Avec deux ou trois retouches on peut très facilement arrivé à ce qui te semble le mieux.

Aussi, j'aurais pu ne pas proposer cela et tout aurait fonctionné aussi bien, c'est juste un petit plus côté optimisation.

a- Enlève le system("pause"). 1 c'est limité à windows. 2. exiger la pause ne sert à rien quant on a appris à utiliser son IDE.
De plus, cela n'a rien à faire dans un tuto très ciblé où les gens sont censés déjà avoir pris leur IDE en main.

lmghs

Pour ça je suis d'accord, il sera retiré lorsque que j'aurais modifié quelques lignes du tutoriel. Cependant je maîtrise mon IDE, tu n'as pas à t'en faire pour ça. Je suis également ok pour les assert.

Pour ce qui est des #define à remplacer par des constantes je préfère continuer à les utiliser (pas de place en mémoire et c'est comme ça que je programme, libre à vous de les changer pour des constantes).

Pour tout ce qui est malloc et calloc le choix m'appartient, je vais éventuellement corriger ce qui semble "incohérent", mais voilà tout. Je veux avant tout proposer quelque chose de très simple.

const ne prend aucune place en mémoire. Si le compilateur voit une expression qui peut être déduite à la compilation, alors c'est une constante de compilation qui sera typée (et qui donnera des messages d'erreur compréhensibles). Si c'est une expression qui ne peut être évaluée qu'à l'exécution, alors const ne fait que flagger la variable comme non-modifiable pour nous aider à mieux réfléchir à ce que l'on fait.
Utiliser #define pour déclarer des constantes ne se justifie qu'en C89.

Si tu veux rendre conforme le realloc au calloc, alors pour assurer l'invariant "pour o allant de strlen+1 à capacity+1, buffer[o]==0", il te faudra rajouter un memset.

Au fait, j'avais oublié dans mon premier message, sizeof(char) == 1 (byte). C'est la définition du byte dans la norme.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte