Partage
  • Partager sur Facebook
  • Partager sur Twitter

Une implémentation de String en C

Qu'en pensez-vous ?

    26 août 2011 à 17:21:37

    Bonjour à tous,


    J'ai commencé à implémenter une gestion sécurisée des chaînes de caractères dynamiques et/ou assez longues, d'une façon un peu « orientée objet » (façon de parler, mais vous verrez que j'ai beaucoup joué dessus dans les identificateurs). Au final, je me retrouve donc avec une sorte d'équivalent en C de la classe String dans d'autres langages OO.
    J'aimerais donc que vous me disiez ce que vous en pensez. C'est encore un brouillon, que je suis en train de compléter / d'améliorer, mais je voudrais savoir si mon approche est bonne, peu adaptée…

    J'ai donc une structure String, qui représente la chaîne de caractère dynamique (donc contenant un tableau dynamique de char, des informations sur la taille, etc.), mais ça ne s'arrête pas là. En effet, j'ai codé ceci comme une liste chaînée de chaînes de caractères dynamiques, ce qui permet d'allouer de moins gros blocs continus de mémoire. D'autre part, cela facilite certaines opérations telles que la concaténation, l'insertion en milieu…
    Je sais que certaines implémentations de la classe std::string en C++ sont assez similaires à la mienne dans l'idée.

    Tout le reste est dit dans les commentaires du header.

    La version la plus récente du projet se trouve ici.


    Header


    #ifndef INCLUDED_STRING_2011_08_25_17_30_MM
    #define INCLUDED_STRING_2011_08_25_17_30_MM
    
    
    #include <stdlib.h>   // for size_t
    #include <stdio.h>    // for FILE
    
    
    
    /*** CONSTANTS ***/
    
    
    #define  STRING_LOG2BLKSZ          8   /* see below */
    /* maximal lenght of a String block */
    #define  STRING_TOTALMAXBLOCKSIZE  (1<<STRING_LOG2BLKSZ)
    /* maximal lenght of a String block, without the terminating '\0' */
    #define  STRING_MAXBLOCKSIZE       (STRING_TOTALMAXBLOCKSIZE-1)
    
    
    
    /*** TYPES ***/
    
    
    /* the String object, to manage large and/or dynamic strings;
       it takes the form of a linked list of datas including an allocated block
       and informations concerning this block and the linked list */
    typedef struct String {   /* single String object */
    	unsigned int blkSize     :STRING_LOG2BLKSZ,   /* used lenght, without
    	                                                 the terminating '\0' */
    	             blkCapacity :STRING_LOG2BLKSZ;   /* allocated lenght - 1 */
    	/* NOTE: Here, "capacity" will mean the allocated lenght minus 1, the
    	   latter being reserved for the terminating '\0'. The "block capacity"
    	   will mean the capacity of the current single String object, when the
    	   "[total] capacity" will mean the sum of the capacities of all the
    	   single Strings composing the linked String object.
    	   Similarly, "size" will mean the used lenght without the '\0'; the
    	   "block size" is the size of the current single String, and the
    	   "[total] size" is the sum of the sizes of the single Strings. */
    	char* blk;   /* the block of string itself (terminating with '\0');
    	                /!\  must be NULL if no memory is allocated */
    	size_t size;   /* total size, counting from this block */
    	struct String* next;   /* next block of the String;
    	                          /!\  must be NULL if this is the last one */
    } String;
    
    
    
    /*** FUNCTIONS ***/
    
    
    /* Creates a new linked String object filled with the content of the string
       passed or, if the 1st argument is NULL, lets it empty but with a specified
       total capacity */
    String* String_new(const char*, size_t);
    
    /* Deletes completely the linked String object */
    void String_del(String*);
    /* Empties the linked String object, setting the total capacity to zero */
    void String_clear(String*);
    
    /* Write the content of the String object on the stream */
    void String_write(const String*, FILE*);
    /* idem, the stream is `stdout` ("displays" the String object) */
    //#define  String_disp(this)  String_write(this, stdout)
    static inline void String_disp(const String* this)
    	{ String_write(this, stdout); }
    
    /* Returns the total size of the linked String */
    static inline size_t String_size(const String* this)
    	{ return (this!=NULL)? this->size : 0; }
    #define  String_lenght  String_size
    /* Returns the total capacity of the linked String */
    size_t String_capacity(const String*);
    
    /* Creates a copy of the linked String without modifying it */
    String* String_copy(const String*);
    
    /* Concatenates the two linked Strings (the 2nd is directly put at the end
       of the 1st, without being copied before) */
    void String_concat(String*, String*);
    /* Concatenates the 1st linked String and a copy of the second, so the 2nd
       is not modified */
    static inline void String_concatCopy(String* this, const String* src)
    	{ String_concat(this, String_copy(src)); }
    
    /* Returns the address of the character of index specified in the linked
       String; returns NULL if the index is out of range */
    char* String_charP(const String*, size_t);
    /* Returns the character of index specified in the linked String; returns
       '\0' if the index is out of range */
    char String_char(const String*, size_t);
    /* Sets the character of index specified in the linked String to the value
       specified; if it is '\0', then this index will become the end of the
       linked String; if the index is out of range, the linked String is not
       modified */
    void String_setChar(String*, size_t, char);
    
    
    #endif
    


    Fichier source


    #include "String.h"
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #include "error.h"
    
    
    
    
    
    /** AUXILIARY FUNCTIONS **/
    
    
    /* returns the power of 2 greater than or equal to `n` */
    static unsigned int pow2sup(unsigned int n) {
    	
    	if(!n)   return 0;
    	unsigned int r= 1;
    	while(n>r)   r<<=1;
    	return r;
    	
            /**
            if(!n || !(n&(n-1))   return n;   // solution found here < http://forum.hardware.fr/hfr/Programmation/C-2/quizz-calculer-superieure-sujet_52944_1.htm#t757526 >
            __asm__ (
                "bsr   eax,   n"
                "mov   n,   eax"
            );
            return 1<<n;
            **/
    }
    
    
    
    
    
    /** MAIN FUNCTIONS **/
    
    
    
    /* Deletes only a single String object */
    static void String_delSingle(String* this) {
    	if(this==NULL)   return;
    	if(this->blk!=NULL)
    		free(this->blk);
    	free(this);
    }
    
    void String_del(String* this) {
    	if(this==NULL)   return;
    	if(this->blk!=NULL)
    		free(this->blk);
    	if(this->next!=NULL)
    		String_del(this->next);
    	free(this);
    }
    
    
    
    void String_clear(String* this) {
    	if(this==NULL)   return;
    	if(this->blk!=NULL) {
    		free(this->blk);
    		this->blk= NULL;
    	}
    	if(this->next!=NULL) {
    		String_del(this->next);
    		this->next= NULL;
    	}
    	this->blkCapacity= 0;
    	this->blkSize= 0;
    	this->size= 0;
    }
    
    
    
    /* Creates only an empty single String object, with the capacity specified;
       /!\  the specified capacity must not be greater than STRING_MAXBLOCKSIZE */
    static String* String_newSingle(size_t blkCapac) {
    	String* r= malloc(sizeof(String));
    	if(r==NULL) {
    		errorMessage("malloc", "when allocating"
    		  " a single String object.");
    		return NULL;
    	}
    	r->blkCapacity= pow2sup(blkCapac)-1;
    	r->blkSize= 0;
    	r->size= 0;
    	r->next= NULL;
    	r->blk= malloc((r->blkCapacity+1)*sizeof(char));
    	if(r->blk==NULL) {
    		errorMessage("malloc", "when allocating"
    		  " the block of a single String object.");
    		String_delSingle(r);
    		return NULL;
    	}
    	*r->blk= '\0';
    	return r;
    }
    
    /* for recursivity in function `String_new` */
    static String* String_new_recur(size_t capacity, const char* s) {
    	size_t blkCapac= (capacity>STRING_MAXBLOCKSIZE)?
    	                 STRING_MAXBLOCKSIZE : capacity;
    	String* r= String_newSingle(blkCapac);
    	if(r==NULL) {
    		errorMessage("String_newSingle", "when allocating"
    		  " a single String of a linked String object.");
    		return NULL;
    	}
    	if(s!=NULL) {
    		r->size= capacity;
    		for(/*r->blkSize=0*/; r->blkSize<blkCapac; ++r->blkSize)
    			r->blk[r->blkSize]= s[r->blkSize];
    		s+= blkCapac;
    	}
    	r->blk[r->blkSize]= '\0';
    	if(capacity>blkCapac) {
    		r->next= String_new_recur(capacity-blkCapac, s);
    		if(r->next==NULL) {
    			/*errorMessage("String_new_recur", "when allocating"
    			  " the following single Strings of a linked String"
    			  " object.");*/
    			String_delSingle(r);
    			return NULL;
    		}
    	}
    	return r;
    }
    
    String* String_new(const char* s, size_t capacity) {
    	return String_new_recur((s!=NULL)? strlen(s) : capacity, s);
    }
    
    
    
    void String_write(const String* this, FILE* stream) {
    	while(this!=NULL) {
    		if(this->blk!=NULL)
    			fputs(this->blk, stream);
    		this= this->next;
    	}
    }
    
    
    
    size_t String_capacity(const String* this) {
    	size_t r;
    	for(r=0; this!=NULL; r+=this->blkCapacity, this=this->next);
    	return r;
    }
    
    
    
    void String_concat(String* this, String* src) {
    	if (this==NULL || src==NULL)   return;
    	while(this->next!=NULL) {
    		this->size+= src->size;
    		this= this->next;
    	}
    	this->size+= src->size;
    	this->next= src;
    }
    
    
    
    String* String_copy(const String* this) {
    	if(this==NULL)   return NULL;
    	String* r;
    	r= String_newSingle(this->blkCapacity);
    	if(r==NULL) {
    		errorMessage("String_newSingle", "when allocating"
    		  " a new single String for a copy of a linked String.");
    		return NULL;
    	}
    	strcpy(r->blk, this->blk);
    	r->blkSize= this->blkSize;
    	r->size= this->size;
    	if(this->next!=NULL) {
    		r->next= String_copy(this->next);
    		if(r->next==NULL) {
    			String_delSingle(r);
    			return NULL;
    		}
    	}
    	return r;
    }
    
    
    
    char* String_charP(const String* this, size_t i) {
    	if(this==NULL || this->size<=i)   return NULL;
    	while(i>=this->blkSize) {
    		i-= this->blkSize;
    		//if(this->next==NULL)   return NULL;
    		this= this->next;
    	}
    	return this->blk+i;
    }
    
    
    
    char String_char(const String* this, size_t i) {
    	if(this==NULL || this->size<=i)   return '\0';
    	while(i>=this->blkSize) {
    		i-= this->blkSize;
    		//if(this->next==NULL)   return '\0';
    		this= this->next;
    	}
    	return this->blk[i];
    }
    
    
    
    void String_setChar(String* this, size_t i, char c) {
    	if(this==NULL || this->size<=i)   return;
    	while(i>=this->blkSize) {
    		i-= this->blkSize;
    		//if(this->next==NULL)   return '\0';
    		this= this->next;
    	}
    	this->blk[i]= c;
    }
    


    Je n'ai pas mis beaucoup de commentaires dans les fonctions, et ils sont tous en anglais (approximatif :-° ). Si vous trouvez que cela fait trop peu, signalez-le moi et je m'efforcerais d'améliorer ça. :)

    Vous remarquerez que de nombreuses fonctions sont récursives. En fait, il est possible de les implémenter sous forme itérative (ce que j'avais commencé à faire), mais je trouve le code moins élégant et plus compliqué. Mais j'ai cru comprendre que le C, contrairement à d'autres langages, était peu adapté/optimisé pour la récursivité (au niveau performance, utilisation de la pile…). Donc, ferais-je mieux de coder les fonctions en itératif pour gagner en performance/solidité ?

    D'autre part, j'utilise des fonctions pour gérer les erreurs. Vous n'avez pas besoin de connaître le corps de ces fonctions, le header correspondant vous suffira :
    #ifndef INCLUDED_ERROR_2011_08_25_17_50_MM
    #define INCLUDED_ERROR_2011_08_25_17_50_MM
    
    
    #include <stdlib.h>   // for size_t
    #include <stdarg.h>   // for va_list
    
    
    /*** FUNCTIONS ***/
    
    /* Writes an error message on stderr, with the name of the file, the line, the
       name of the function in which the error occured, and optionally the name of
       the called function which signaled the error (NULL if not wanted), then an
       optional message in the format of `printf` (NULL if not wanted). */
    void errorMessage(const char* file, size_t line, const char* function,
                      const char* calledFunction, const char* msgFmt, ...);
    void errorMessage_v(const char* file, size_t line, const char* function,
                        const char* calledFunction, const char* msgFmt, va_list ap);
    
    /* Calls the function aboce and then quits the program with `exit` and the value
       `EXIT_FAILURE` */
    void errorQuit(const char* file, size_t line, const char* function,
                   const char* calledFunction, const char* msgFmt, ...);
    
    /* macros designed to simplify the use of the functions above */
    #define  errorMessage(called, ...)  \
      errorMessage(__FILE__, __LINE__, __func__, called, __VA_ARGS__)
    #define  errorMessage_v(called, fmt, ap)  \
      errorMessage_v(__FILE__, __LINE__, __func__, called, fmt, ap)
    #define  errorQuit(called, ...)  \
      errorQuit(__FILE__, __LINE__, __func__, called, __VA_ARGS__)
    
    
    #endif
    


    Ah oui, j'allais oublier : voici une petite macro bien crade et bien utile pour faire des tests :
    /* useful macro which prints on stdin the name (or the expression) of the String
       passed, its size, its capacity and its content */
    #define DISPSTR(str)   do{\
    	const String* _= (str);   \
    	if(_==NULL)   puts(#str" is NULL.");   \
    	else          printf(#str" (%u/%u): \"", String_size(_), String_capacity(_)), String_disp(_), puts("\"");   \
    	               }while(0)
    



    Merci d'avance pour vos critiques !
    • Partager sur Facebook
    • Partager sur Twitter
      26 août 2011 à 17:40:42

      J'ai juste parcouru, et j'ai un question.

      Pourquoi une liste chainée pour le stockage?
      Ca semble très couteux, non?

      edit:

      Citation : Maelan


      En effet, j'ai codé ceci comme une liste chaînée de chaînes de caractères dynamiques, ce qui permet d'allouer de moins gros blocs continus de mémoire. D'autre part, cela facilite certaines opérations telles que la concaténation, l'insertion en milieu…


      J'ai donc parcouru un peu vite. :'(
      Je ne sais pas si les implémentations de string en c++ se font via une liste.

      Tu pourrais cacher ta structure aussi, pour éviter qu'un utilisateur n'accède directement aux membres.
      Vu que tu passes déjà par des accesseur/mutateurs, ça me semble naturel.

      edit2:
      un détail
      dans ton header
      #include <stdlib.h>   // for size_t
      

      stddef.h suffit.
      • Partager sur Facebook
      • Partager sur Twitter
      Zeste de Savoir, le site qui en a dans le citron !
        26 août 2011 à 18:14:28

        Citation : GurneyH

        Pourquoi une liste chainée pour le stockage?
        Ça semble très couteux, non?

        edit:

        Citation : Maëlan

        En effet, j'ai codé ceci comme une liste chaînée de chaînes de caractères dynamiques, ce qui permet d'allouer de moins gros blocs continus de mémoire. D'autre part, cela facilite certaines opérations telles que la concaténation, l'insertion en milieu…


        J'ai donc parcouru un peu vite. :'(
        Je ne sais pas si les implémentations de string en c++ se font via une liste.



        Ah, ça y est, je crois que j'ai retrouvé là où j'ai vu ça :

        Citation : FAQ C++ de Developpez.com − Les chaînes de caractères − « Quelles précautions faut-il prendre avec string::c_str() et string::data() ? »

        Il ne faut faire aucune hypothèse quant à la façon dont est implémentée la classe string. Le comportement de cette dernière est spécifique à presque chaque version de compilateur C++ existant.
        Par exemple, les caractères peuvent ne pas être stockés en interne de manière contiguë (on peut envisager un système de concaténation rapide via un chaînage de sous chaînes de caractères).
        Ou encore, certaines implémentations utilisent le Copy On Write (COW) qui implique que plusieurs objets string peuvent en interne partager le même espace mémoire pour stocker leurs caractères.
        […]

        (Le reste n'a strictement rien à voir.)


        Citation : GurneyH

        Tu pourrais cacher ta structure aussi, pour éviter qu'un utilisateur n'accède directement aux membres.
        Vu que tu passes déjà par des accesseur/mutateurs, ça me semble naturel.


        Tu penses à ça ? Je vais voir, mais ça risque de compliquer un poil pour les quelques fonctions inlines qui sont définies dans le header (finalement, ce n'est peut-être pas plus mal, ça optimise peut-être mais ce n'est pas très beau je trouve).


        Citation : GurneyH

        dans ton header

        #include <stdlib.h>   // for size_t
        

        stddef.h suffit.


        C'est vrai. :)


        Merci pour tes commentaires !
        • Partager sur Facebook
        • Partager sur Twitter
          26 août 2011 à 18:25:36

          Salut,

          J'ai peut-être regardé un peu vite, mais il y a une question qui me vient à l'esprit quand je lis que tu as implémenter cela sous forme de liste chaînée: comment fais-tu lorsque l'utilisateur souhaite récupéré la chaîne entière? Parce que bon, il faudra quand même bien récupéré la chaîne de caractère sous forme d'un pointeur sur char à un moment ou un autre, pour l'afficher avec printf par exemple.
          • Partager sur Facebook
          • Partager sur Twitter
            26 août 2011 à 18:37:37

            Citation : Taurre

            J'ai peut-être regardé un peu vite, mais il y a une question qui me vient à l'esprit quand je lis que tu as implémenter cela sous forme de liste chaînée: comment fais-tu lorsque l'utilisateur souhaite récupéré la chaîne entière? Parce que bon, il faudra quand même bien récupéré la chaîne de caractère sous forme d'un pointeur sur char à un moment ou un autre, pour l'afficher avec printf par exemple.


            Ben, je vais coder une fonction pour ça, un peu comme la méthode c_str de la classe std::string en C++.
            Et pour l'affichage, j'ai déjà fait une fonction qui écrit le contenu dans un fichier.

            -----
            J'étais en train de m'interroger sur l'intérêt d'une fonction qui « normaliserait » la chaîne de caractère globale, en faisant en sorte que tous les blocs sauf le dernier soient utilisés jusqu'à la taille maximale autorisée (parce qu'avec diverses opérations comme des insertions, des concaténations, des suppressions… on peut se retrouver avec des blocs de tailles variées). En gros, qui « tasserait » le contenu pour en faire un beau patté bien uniforme.
            • Partager sur Facebook
            • Partager sur Twitter
              26 août 2011 à 18:42:12

              Citation : Maëlan

              J'ai commencé à implémenter une gestion sécurisée des chaînes de caractères dynamiques et/ou assez longues, d'une façon un peu « orientée objet »


              Joli projet.

              Je trouve les commentaires du fichier d'entête un peu denses. Quelques lignes vides aéreraient agréablement. Les commentaires comme /**** FUNCTIONS ****/ peuvent aussi être supprimés, car visuellement, l'espace blanc est le meilleur séparateur.

              Les commentaires parlent parfois de String object et parfois de linked String object. Il faut laisser tomber le linked de la documentation utilisateur, car c'est un détail d'implémentation.

              Il manque un const dans void String_concat(String*, String*);

              Dans String_write le fputs ajoute des retour à la ligne non voulus, non?
              • Partager sur Facebook
              • Partager sur Twitter
                26 août 2011 à 18:51:51

                Citation : Marc Mongenet


                Dans String_write le fputs ajoute des retour à la ligne non voulus, non?



                fputs n'a pas le même comportement que puts sur ce point.

                #include <stdio.h>
                
                int main(void)
                {
                    fputs("Hello World", stdout);
                    fputs("Hello World\n", stdout);
                
                    puts("Hello World");
                    puts("Hello World\n");
                
                    return 0;
                }
                

                Hello WorldHello World
                Hello World
                Hello World
                
                
                Process returned 0 (0x0)   execution time : 0.141 s
                Press any key to continue.
                • Partager sur Facebook
                • Partager sur Twitter
                Zeste de Savoir, le site qui en a dans le citron !
                  26 août 2011 à 19:12:40

                  Citation : Maëlan


                  Ben, je vais coder une fonction pour ça, un peu comme la méthode c_str de la classe std::string en C++.
                  Et pour l'affichage, j'ai déjà fait une fonction qui écrit le contenu dans un fichier.



                  Ah oui, je n'avais pas fait attention à la fonction String_write.
                  Sinon, j'ai une autre question: tu dis que tu utilises des liste chaînées pour facilité les insertions. C'est vrai, mais seulement si tu dois insérer un bloc entre deux autre. Comment fais-tu si l'utilisateur souhaites ajouter une chaîne en plein milieu d'un bloc existant?
                  • Partager sur Facebook
                  • Partager sur Twitter
                    26 août 2011 à 19:29:12

                    Citation : Marc Mongenet

                    Joli projet.

                    Merci :)

                    Citation : Marc Mongenet

                    Je trouve les commentaires du fichier d'entête un peu denses. Quelques lignes vides aéreraient agréablement. Les commentaires comme /**** FUNCTIONS ****/ peuvent aussi être supprimés, car visuellement, l'espace blanc est le meilleur séparateur.

                    Qu'entends-tu par « denses » ? Trop fournis, ou juste pas assez aérés ?

                    Citation : Marc Mongenet

                    Les commentaires parlent parfois de String object et parfois de linked String object. Il faut laisser tomber le linked de la documentation utilisateur, car c'est un détail d'implémentation.


                    Tu rejoins GurneyH. Tu as raison, mais je ne le ferais que lorsque le fonctionnement interne et donc la structure sera cachée. Pour l'instant, ça m'aide surtout en interne, pour différencier ce qui agit sur toute la chaîne de ce qui n'agit que sur un maillon.
                    À ce propos, vous avez vu que j'ai employé un certain nombre de fonctions statiques dans String.c, notamment pour agir sur un seul maillon. Ça ne me paraît pas très beau, bien que je ne pourrais pas dire pourquoi. Un avis ?
                    D'ailleurs, j'emploie les termes « single String » et « linked String » en anglais, je ne sais pas trop s'ils sont bien adaptés (j'ai un petit problème de vocabulaire, il y a aussi « block » que j'emploie un peu n'importe comment).

                    Citation : Marc Mongenet

                    Il manque un const dans void String_concat(String*, String*);


                    Je me suis interrogé sur ce point. J'ai préféré ne pas le mettre car cette fonction concatène directement le 2è argument au 1er, donc même s'il n'est pas modifié sur le coup, il risque de l'être dans le maniement futur du 1er argument. Ça permet de bien marquer la différence avec la fonction String_concatCopy qui se trouve juste en dessous.

                    Citation : Marc Mongenet

                    Dans String_write le fputs ajoute des retour à la ligne non voulus, non?


                    GurneyH a déjà répondu. fputs ne place pas de '\n' final, contrairement à puts.

                    -----
                    Je vous remercie déjà de toutes vos réponses constructives.
                    Sinon, avez-vous des remarques de fond (c'est surtout ce qui m'intéresse, à vrai dire) ? N'ai-je pas pris une mauvaise direction, par exemple ?


                    ÉDIT:

                    Citation : Taurre

                    Tu dis que tu utilises des liste chaînées pour facilité les insertions. C'est vrai, mais seulement si tu dois insérer un bloc entre deux autre. Comment fais-tu si l'utilisateur souhaites ajouter une chaîne en plein milieu d'un bloc existant ?


                    J'envisage de tronquer le bloc à l'endroit de l'insertion. Le contenu inséré sera simplement inséré comme le(s) maillon(s) suivant(s), et la fin du bloc coupé ira encore dans un maillon à part après. C'est peut-être un peu bourrin (on risque de se retrouver avec plein de tous petits blocs) mais c'est simple et rapide. Il sera peut-être possible de mettre en place quelques optimisations pour éviter de se retrouver avec deux blocs de 16 chars en les réunissant, par exemple.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      26 août 2011 à 20:10:36

                      Personellement, j’aurais formatté le code comme ça — je trouve ça plus aèré et plus clair :

                      /**
                       * Types
                       */
                      
                      /**
                       * the String object, to manage large and/or dynamic strings; it takes the form
                       * of a linked list of datas including an allocated block and informations
                       * concerning this block and the linked list.
                       */
                      typedef struct String {
                        /*
                         * Used length, without the terminating '\0'.
                         */
                        unsigned int blkSize     :STRING_LOG2BLKSZ;
                      
                        /*
                         * Allocated length - 1
                         */
                        unsigned int blkCapacity :STRING_LOG2BLKSZ;
                      
                        /*
                         * NOTE: Here, "capacity" will mean the allocated lenght minus 1, the latter
                         * being reserved for the terminating '\0'. The "block capacity will mean the
                         * capacity of the current single String object, when the "[total] capacity"
                         * will mean the sum of the capacities of all the single Strings composing the
                         * linked String object. Similarly, "size" will mean the used lenght without
                         * the '\0'; the "block size" is the size of the current single String, and
                         * the "[total] size" is the sum of the sizes of the single Strings.
                         */
                      
                        /*
                         * The block of string itself (terminating with '\0').
                         *
                         * Beware, this *must* be NULL if no memory is allocated.
                         */
                        char* blk;
                      
                        /*
                         * Total size, counting from this block.
                         */
                        size_t size;
                      
                        /*
                         * Next block of the string.
                         *
                         * Beware, this *must* be NULL if this is the last string.
                         */
                        struct String* next;
                      } String;
                      
                      /**
                       * Functions.
                       */
                      
                      /*
                       * Creates a new linked String object filled with the content of the string
                       * passed or, if the 1st argument is NULL, lets it empty but with a specified
                       * total capacity
                       */
                      String* String_new(const char*, size_t);
                      


                      En passant, on dit length et pas lenght — cette dernière orthographe est une faute de frappe très fréquente par contre, faisant que je préfère utiliser size :p
                      • Partager sur Facebook
                      • Partager sur Twitter
                        26 août 2011 à 20:41:43

                        Citation : Mon ouïe

                        Personellement, j’aurais formatté le code comme ça — je trouve ça plus aèré et plus clair :
                        […]


                        Chacun ses goûts, après. :) Par contre, tu fais des indentations de 2 espaces, personnellement je trouve que c'est trop peu (ça fait trop tassé à gauche et partant, pas assez lisible). J'aime bien 3 espaces (ou de plus en plus 4). Je trouve que '\t' est affiché beaucoup trop large par les navigateurs Web. :(

                        Citation : Mon ouïe

                        En passant, on dit length et pas lenght — cette dernière orthographe est une faute de frappe très fréquente par contre, faisant que je préfère utiliser size :p

                        Arghh, il ne me reste plus qu'à inverser ces deux malheureuses lettres un peu partout. Merci pour la remarque ! (Et au fait, je pense comme toi pour l'utilisation de size.)


                        Revoilà le header avec une (je l'espère) meilleure mise en forme. J'ai surtout passé des lignes et réorganisé un peu la position des commentaires, je pense que ça suffit. Pas de changements ou de nouveautés pour l'instant.
                        #ifndef INCLUDED_STRING_2011_08_25_17_30_MM
                        #define INCLUDED_STRING_2011_08_25_17_30_MM
                        
                        
                        #include <stdlib.h>   // for size_t
                        #include <stdio.h>    // for FILE
                        
                        
                        
                        
                        
                        /*** CONSTANTS ***/
                        
                        
                        
                        /* see below */
                        #define  STRING_LOG2BLKSZ          8
                        
                        /* maximal lenght of a String block */
                        #define  STRING_TOTALMAXBLOCKSIZE  (1<<STRING_LOG2BLKSZ)
                        
                        /* maximal lenght of a String block, without the terminating '\0' */
                        #define  STRING_MAXBLOCKSIZE       (STRING_TOTALMAXBLOCKSIZE-1)
                        
                        
                        
                        
                        
                        /*** TYPES ***/
                        
                        
                        
                        /* the String object, to manage large and/or dynamic strings;
                           it takes the form of a linked list of datas including an allocated block
                           and informations concerning this block and the linked list */
                        typedef struct String {
                        	/* used length, without the terminating '\0' */
                        	unsigned int  blkSize      :STRING_LOG2BLKSZ,
                        	/* allocated length - 1 */
                        	              blkCapacity  :STRING_LOG2BLKSZ;
                        	/* the block itself (terminating with '\0');
                        	   /!\  must be NULL if no memory is allocated */
                        	char*  blk;
                        	/* total size, counting from this block */
                        	size_t  size;
                        	/* next block of the String;
                        	   /!\  must be NULL if this is the last one */
                        	struct String*  next;
                        } String;
                        
                        /* NOTE:
                         * Here, "capacity" will mean the allocated length minus 1, the latter being
                         * reserved for the terminating '\0'. The "block capacity"  will mean the
                         * capacity of the current single String object, when the "[total] capacity"
                         * will mean the sum of the capacities of all the single Strings composing the
                         * linked String object.
                         * Similarly, "size" will mean the used length without the '\0'; the "block
                         * size" is the size of the current single String, and the "[total] size" is the
                         * sum of the sizes of the single Strings.
                         */
                        
                        
                        
                        
                        
                        /*** FUNCTIONS ***/
                        
                        
                        
                        /* Creates a new linked String object filled with the content of the string
                           passed or, if the 1st argument is NULL, lets it empty but with a specified
                           total capacity */
                        String* String_new(const char*, size_t);
                        
                        
                        
                        /* Deletes completely the linked String object */
                        void String_del(String*);
                        
                        /* Empties the linked String object, setting the total capacity to zero */
                        void String_clear(String*);
                        
                        
                        
                        /* Write the content of the String object on the stream */
                        void String_write(const String*, FILE*);
                        
                        /* idem, the stream is `stdout` ("displays" the String object) */
                        static inline void String_disp(const String* this)
                        	{ String_write(this, stdout); }
                        
                        
                        
                        /* Returns the total size of the linked String */
                        static inline size_t String_size(const String* this)
                        	{ return (this!=NULL)? this->size : 0; }
                        /* idem */
                        #define  String_length  String_size
                        
                        /* Returns the total capacity of the linked String */
                        size_t String_capacity(const String* this);
                        
                        
                        
                        /* Creates a copy of the linked String without modifying it */
                        String* String_copy(const String*);
                        
                        
                        
                        /* Concatenates the two linked Strings (the 2nd is directly put at the end
                           of the 1st, without being copied before) */
                        void String_concat(String*, String*);
                        
                        /* Concatenates the 1st linked String and a copy of the second, so the 2nd
                           is not modified */
                        static inline void String_concatCopy(String* this, const String* src)
                        	{ String_concat(this, String_copy(src)); }
                        
                        
                        
                        /* Returns the address of the character of index specified in the linked
                           String; returns NULL if the index is out of range */
                        char* String_charP(const String*, size_t);
                        
                        /* Returns the character of index specified in the linked String; returns
                           '\0' if the index is out of range */
                        char String_char(const String*, size_t);
                        
                        /* Sets the character of index specified in the linked String to the value
                           specified; if it is '\0', then this index will become the end of the
                           linked String; if the index is out of range, the linked String is not
                           modified */
                        void String_setChar(String*, size_t, char);
                        
                        
                        #endif
                        
                        Effectivement, en voyant ça sous Firefox, c'est plus lisible.
                        • Partager sur Facebook
                        • Partager sur Twitter
                          2 novembre 2011 à 13:45:26

                          Bonjour,

                          J'avais complètement oublié ce sujet… Pourtant, mon projet a bien évolué depuis le dernier message, c'est pourquoi je me permets de le poster à nouveau ici.

                          Finalement, j'ai revu mon implémentation. Il y a toujours une liste chaînée de sous-chaînes (des « blocs »), mais celle-ci est englobée dans une autre structure. Ça complique un poil certaines opérations mais en contrepartie cest plus pratique (blocs indépendants, on n'a pas à mettre à jour la taille/capacité de chaque bloc un par un…).
                          J'ai mis en place plusieurs optimisations, la plus remarquable étant un buffer statique pour accélérer les opérations avec de petites chaînes.

                          Je n'ai pas fini mais le projet est actuellement au point mort (j'espère pouvoir le reprendre bientôt). Il me reste encore des fonctions de base à coder. Je dois aussi revoir certains points, notamment la gestion des cas particuliers (NULL, chaîne vide…) et des erreurs d'allocation mémoire (ainsi que les fonctions inline). J'envisage aussi (pour plus tard) un module de gestion de la mémoire plus poussé, avec des wrappers pour malloc & co., une liste chaînée globale des Strings allouées…

                          J'ai prêté plus d'attention à la présentation cette fois. :p


                          Header


                          /**
                          ***  String.h
                          ***
                          ***    module:   String  −  header file
                          ***    function: provides the `String` object type (abstract data type) and
                          ***              functions for using it; the `String` object type is designed to
                          ***              manage dynamic and/or large characters strings.
                          ***              This header is the interface file of the module, it provides
                          ***              the incomplete type declaration of `String` (because `String`
                          ***              is an ADT) and the prototype of the usable functions.
                          ***    author:   Maëlan (aka Maëlan44)
                          ***              (see http://www.siteduzero.com/membres-294-232877.html )
                          ***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
                          ***
                          ***    last modified: 09/07/2011, 15:15
                          ***
                          **/
                          
                          #ifndef INCLUDED_STRING_2011_08_25_17_30_MM
                          #define INCLUDED_STRING_2011_08_25_17_30_MM
                          
                          
                          #include <stddef.h>   // for size_t
                          #include <stdio.h>    // for FILE
                          
                          
                          
                          
                          
                          /*** TYPES ***/
                          
                          
                          
                          /* the String object, to manage large and/or dynamic strings;
                             it takes the form of a linked list of datas including an allocated block
                             and informations concerning this block and the linked list */
                          typedef  struct String  String;
                          
                          
                          
                          
                          
                          /*** FUNCTIONS ***/
                          
                          
                          
                          /* Creates a String object filled with the content of the string passed; if NULL
                             is passed,  lets it null (ie. empty and with no capacity) */
                          String* String_new(const char*);
                          
                          /* Creates an empty String object with at least the capacity specified */
                          String* String_newEmpty(size_t);
                          
                          /* Creates a new String object that is a copy of the passed one */
                          String* String_copy(const String*);
                          
                          /* Deletes completely the String object */
                          void String_del(String*);
                          
                          
                          
                          /* Returns the size of the String */
                          /*extern inline*/  size_t String_size(const String*);
                          /* idem */
                          #define  String_length  String_size
                          
                          /* Returns the capacity of the String */
                          /*extern inline*/  size_t String_capacity(const String*);
                          
                          /* boolean: Is the String empty (ie. the size is zero)? */
                          /*extern inline*/  int String_isEmpty(const String*);
                          
                          /* boolean: Is the String null (ie. the size and capacity are zero)? */
                          /*extern inline*/  int String_isNull(const String*);
                          
                          /* Empties the String, setting its size to zero without altering its capacity */
                          void String_empty(String*);
                          
                          /* Empties the String, setting its size and capacity to zero */
                          void String_clear(String*);
                          
                          
                          
                          /* Returns the character of index specified in the String; returns '\0' if the
                             index is out of range */
                          char String_char(/*const*/ String*, size_t);
                          
                          /* Sets the character of index specified in the String to the value specified;
                             if it is '\0', then this index will become the end of the String; if the
                             index is out of range, the linked String is not modified */
                          void String_setChar(String*, size_t, char);
                          
                          
                          
                          /* compare the two Strings the same way as strcmp does */
                          int String_cmp(const String*, const String*);
                          
                          /* compare the String object with the ”regular” characters string, the same way
                             as strcmp does */
                          int String_cmpCStr(const String*, const char*);
                          
                          /* returns the index of the 1st occurrence of the character in the String, or a
                             negative number if the the character is no found */
                          ptrdiff_t String_searchChar(const String*, char c);
                          
                          /* returns the index of the last occurrence of the character in the String, or a
                             negative number if the the character is no found */
                          ptrdiff_t String_searchLastChar(const String*, char c);
                          
                          
                          
                          /* Concatenates a copy of the 2nd String object to the first; returns the final
                             String, or NULL if there was an error */
                          String* String_concat(String*, const String*);
                          
                          
                          
                          /* Write the content of the String on the stream */
                          void String_fput(const String*, FILE*);
                          
                          /* idem, the stream is `stdout` */
                          /*extern inline*/  void String_put(const String*);
                          
                          
                          
                          /* Returns a buffer containing the content of the String in the form of a plain
                             characters string; this buffer shall not be modified by the user, and it may
                             be modified by the String functions to stay up-to-date or be freed if it is
                             no longer the case */
                          const char* String_cStr(String*);
                          
                          /* Returns the buffer containing the "cstr" of the String if it exists and is
                             up-to-date, NULL otherwise */
                          /*extern inline*/  const char* String_getCStr(const String*);
                          
                          
                          #endif
                          



                          Fichiers sources


                          /**
                          ***  String.c
                          ***
                          ***    module:   String  −  source file
                          ***    function: provides the `String` object type (abstract data type) and
                          ***              functions for using it; the `String` object type is designed to
                          ***              manage dynamic and/or large characters strings.
                          ***              This file is the main source file of the module, it contains
                          ***              the full declaration of the type `String`, as well as others
                          ***              types and additionnal function declarations (static), both used
                          ***              internally by the implementation. Of course it contains (in
                          ***              fact, includes) also the definitions of all the functions.
                          ***    author:   Maëlan (aka Maëlan44)
                          ***              (see http://www.siteduzero.com/membres-294-232877.html )
                          ***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
                          ***
                          ***    last modified: 09/06/2011, 18:40
                          ***
                          **/
                          
                          #include "String.h"
                          #include <stdlib.h>
                          #include <string.h>
                          #include <stdio.h>
                          #include "error.h"
                          
                          
                          
                          
                          
                          /*** CONSTANTS ***/
                          
                          
                          
                          /* see below */
                          #define  STRING_LOG2BLKSZ      8
                          
                          /* maximal lenght of the buffer of a String block */
                          #define  STRING_TOTALMAXBLKSZ  (1<<STRING_LOG2BLKSZ)
                          
                          /* maximal lenght of the buffer of a String block, without the terminating '\0' */
                          #define  STRING_MAXBLKSZ       (STRING_TOTALMAXBLKSZ-1)
                          
                          /* lenght of the static buffer of a String block; it is designed for a struct of
                             type `StringBlock` to fit in 32 bytes */
                          //#define  STRING_TOTALBUFSZ     ( (32-sizeof(*char)-2*STRING_LOG2BLKSZ/8)       \
                                                           / sizeof(char) )
                          #define STRING_TOTALBUFSZ 19
                          
                          /* lenght of the static buffer of a String block, without the terminating '\0' */
                          #define  STRING_BUFSZ          (STRING_TOTALBUFSZ-1)
                          
                          
                          
                          
                          
                          /*** TYPES ***/
                          
                          
                          
                          /* NOTE:
                           * Here, "block capacity" will mean the allocated length for the block minus 1,
                           * the latter being reserved for the terminating '\0'. The "String capacity" or
                           * "total capacity" will mean the sum of the capacities of all the blocks
                           * composing the String.
                           * Similarly, "block size" will mean the used length without the '\0'. The
                           * "String size" of "total size" is the sum of the sizes of all the blocks.
                           */
                          
                          
                          
                          /* a String block, link of a linked list;
                             contains a buffer and informations for using it */
                          typedef struct StringBlk {
                          	/* static buffer: optimisation for short strings or string blocks
                          	   [OPT:buf] */
                          	char buf[STRING_TOTALBUFSZ];
                          	/* buffer; shall point to the member `buf` if it is used, else to the
                          	   allocated memory
                          	   /!\  Shall never be NULL! */
                          	char* p;
                          	/* block size (shall be lesser than or equal to the member `capac`) */
                          	unsigned int size   :STRING_LOG2BLKSZ,
                          	/* block capacity (shall be at least STRING_BUFSZ, since it is the size
                          	   of the static buffer; can not be greater than STRING_MAXBLKSZ) */
                          	             capac  :STRING_LOG2BLKSZ;
                          	/* block following this one in the linked list */
                          	struct StringBlk* next;
                          } StringBlk;
                          
                          
                          
                          /* the String object itself;
                             it takes the form of a linked list of blocks, each including a buffer and
                             informations concerning it */
                          struct String {
                          	/* total length */
                          	size_t  size,
                          	/* total capacity */
                          	        capac;
                          	/* first block of the string */
                          	StringBlk *first,
                          	/* last block: optimisation for operations on the end of the string,
                          	   such as concatenating or adding a character [OPT:last] */
                          	          *last;
                          	/* optimisation: a cursor pointing to the last character read [OPT:cur] */
                          	struct {   size_t pos;   /* index of the 1st char. of the block pointed */
                          	           StringBlk* blk;
                          	       } cur;
                          	/* buffer containing the full character string obtained via
                          	   `String_cStr` (and returned as constant); shall be NULL if this
                          	   function had not be called yet, or if the content is outdated (in
                          	   this case, it shall be freed if needed).
                          	   For reasons of optimisation, this buffer can be that of the first
                          	   block of the String, in which case it shall not be freed.
                          	   If the buffer exists, it can be used to perform optimisations on
                          	   operations involving reading content [OPT:cstr]. */
                          	char* cStr;
                          };
                          
                          
                          
                          
                          
                          /*** AUXILIARY FUNCTIONS ***/
                          
                          
                          
                          /* returns the power of 2 greater than or equal to `n` */
                          static  unsigned int pow2sup(unsigned int n) {
                          	
                          	if(!n)   return 0;
                          	unsigned int r= 1;
                          	while(n>r)   r<<=1;
                          	return r;
                          	
                                  /**   // solution found here < http://forum.hardware.fr/hfr/Programmation/C-2/quizz-calculer-superieure-sujet_52944_1.htm#t757526 >
                                  if(!n || !(n&(n-1))   return n;
                                  __asm__ (
                                      "bsr   eax,   n"
                                      "mov   n,   eax"
                                  );
                                  return 1<<n;
                                  **/
                          }
                          
                          
                          
                          
                          
                          /*** MAIN FUNCTIONS ***/
                          
                          
                          
                          /** functions for object `StringBlock` **/
                          
                          
                          /* Creates a new String block filled with the content of the string passed, or
                             at most its STRING_MAXBLKSZ first characters; the second argument must be the
                             size of the string passed */
                          static  StringBlk* StringBlk_new(const char*, size_t);
                          
                          /* Creates a new empty String block with at least the specified capacity */
                          static  StringBlk* StringBlk_newEmpty(unsigned int);
                          
                          /* Creates a copy of the String block (ie. a new String block filled with the
                             content of the one passed) */
                          static inline  StringBlk* StringBlk_copy(const StringBlk*);
                          
                          /* Deletes the String block and all the following String blocks of the linked
                             list to which it belongs */
                          static  void StringBlk_del(StringBlk*);
                          
                          /* boolean: Does the String block uses its static buffer? */
                          static inline  int StringBlk_useStaticBuf(const StringBlk* this);
                          
                          
                          #include "String.c.1"
                          
                          
                          
                          /** functions for object `String` **/
                          
                          
                          /* boolean: Has the String an existing "cstr"? */
                          static inline  int String_hasCStr(const String*);
                          
                          /* boolean: Has the String an existing "cstr", the buffer of which is also that
                             of the 1st block? */
                          static inline  int String_hasSharedCStr(const String*);
                          
                          /* boolean: Has the String an existing "cstr", the buffer of which is allocated
                             separately? */
                          static inline  int String_hasSeparateCStr(const String*);
                          
                          /* Delete the "cstr" of the String */
                          static inline  void String_delCStr(String*);
                          
                          
                          #include "String.c.2"
                          

                          /**
                          ***  String.c.1
                          ***
                          ***    module:   String  −  source file (part 1)
                          ***    function: provides the `String` object type (abstract data type) and
                          ***              functions for using it; the `String` object type is designed to
                          ***              manage dynamic and/or large characters strings.
                          ***              This file contains the definitions of functions working on the
                          ***              `StringBlock` object type.
                          ***    author:   Maëlan (aka Maëlan44)
                          ***              (see http://www.siteduzero.com/membres-294-232877.html )
                          ***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
                          ***
                          ***    last modified: 09/07/2011, 20:45
                          ***
                          **/
                          
                          
                          
                          
                          
                          static  StringBlk* StringBlk_newEmpty(unsigned int capac) {
                          	StringBlk* r =  malloc(sizeof(StringBlk));
                          	if(r==NULL) {
                          		errorMessage("malloc", "when allocating a String block");
                          		return NULL;
                          	}
                          	
                          	if(capac<=STRING_BUFSZ) {
                          		r->p =      r->buf;
                          		r->capac =  STRING_BUFSZ;
                          	} else {
                          		r->capac =  (capac>STRING_MAXBLKSZ)? STRING_MAXBLKSZ : capac;
                          		//r->capac =  log2sup(r->blk.capac) - 1;
                          		r->p =  malloc((r->capac+1)*sizeof(char));
                          		if(r->p == NULL) {
                          			errorMessage("malloc", "when allocating a dynamic"
                          			 " buffer for a String block");
                          			free(r);
                          			return NULL;
                          		}
                          	}
                          	
                          	*r->p =    '\0';
                          	r->size =  0;
                          	//r->next =      NULL;
                          	
                          	return r;
                          }
                          
                          
                          
                          static  StringBlk* StringBlk_new(const char* s, size_t size) {
                          	StringBlk* r;
                          	
                          	//if(s==NULL)    size =  0;
                          	r =  StringBlk_newEmpty(size);
                          	if(r==NULL)    return NULL;
                          	
                          	r->size =  (size > r->capac)? r->capac : size;
                          	/*if(s!=NULL)    */strncpy(r->p, s, r->size);
                          	r->p[r->size] =  '\0';
                          	
                          	return r;
                          }
                          
                          
                          
                          static inline  StringBlk* StringBlk_copy(const StringBlk* this)
                          	{ //if(this==NULL)    return NULL;
                          	return StringBlk_new(this->p, this->size); }
                          
                          
                          
                          static  void StringBlk_del(StringBlk* this) {
                          	StringBlk* blk;
                          	while(this!=NULL) {
                          		blk =   this;
                          		this =  this->next;
                          		if(!StringBlk_useStaticBuf(blk))
                          			free(blk->p);
                          		free(blk);
                          	}
                          }
                          
                          
                          
                          
                          
                          static inline  int StringBlk_useStaticBuf(const StringBlk* this)
                          	{ return this->p == this->buf; }
                          

                          /**
                          ***  String.c.2
                          ***
                          ***    module:   String  −  source file (part 2)
                          ***    function: provides the `String` object type (abstract data type) and
                          ***              functions for using it; the `String` object type is designed to
                          ***              manage dynamic and/or large characters strings.
                          ***              This file contains the definitions of functions working on the
                          ***              `String` object type.
                          ***    author:   Maëlan (aka Maëlan44)
                          ***              (see http://www.siteduzero.com/membres-294-232877.html )
                          ***    website:  http://www.siteduzero.com/forum-83-683766-p1-une-implementation-de-string-en-c.html
                          ***
                          ***    last modified: 09/07/2011, 18:50
                          ***
                          **/
                          
                          
                          
                          
                          
                          /**  CREATION, DELETION
                           **    newEmpty, new, copy, del
                           **/
                          
                          
                          
                          String* String_newEmpty(size_t capac) {
                          	String* r;
                          	StringBlk** p;
                          
                          	r =  malloc(sizeof(String));
                          	if(r==NULL) {
                          		errorMessage("malloc", "when allocating a String");
                          		return NULL;
                          	}
                          	
                          	r->capac =  0;
                          	p =  &r->first;
                          	do {    /* at least one block (so STRING_BUFSZ) is created, even if the
                          	           capacity required is 0 */
                          		*p =  StringBlk_newEmpty(capac-r->capac);
                          		if(*p==NULL) {
                          			errorMessage("StringBlk_newEmpty", "when creating a"
                          			 " String block");
                          			String_del(r);
                          			return NULL;
                          		}
                          		r->capac +=  (*p)->capac;
                          		if(capac > r->capac)    p =  &(*p)->next;
                          	} while(capac > r->capac);
                          	
                          	r->last =     *p;
                          	(*p)->next =  NULL;
                          	
                          	r->size =     0;
                          	r->cur.pos =  0;
                          	r->cur.blk =  r->first;
                          	r->cStr =     r->first->p;
                          	
                          	return r;
                          }
                          
                          
                          
                          String* String_new(const char* s) {
                          	String* r;
                          	
                          	r =  malloc(sizeof(String));
                          	if(r==NULL) {
                          		errorMessage("malloc", "when allocating a String");
                          		return NULL;
                          	}
                          	
                          	r->size =   0;
                          	r->capac =  0;
                          	if(s==NULL) {
                          		r->first =  NULL;
                          		r->last =   NULL;
                          	} else {
                          		size_t size =  strlen(s);
                          		StringBlk** p =  &r->first;
                          		do {
                          			*p =  StringBlk_new(s+r->size, size-r->size);
                          			if(*p==NULL) {
                          				errorMessage("StringBlk_new", "when creating a"
                          				 " String block");
                          				String_del(r);
                          				return NULL;
                          			}
                          			r->capac +=  (*p)->capac;
                          			r->size +=   (*p)->size;
                          			if(size != r->size)    p =  &(*p)->next;
                          		} while(size != r->size);
                          		r->last =     *p;
                          		(*p)->next =  NULL;
                          	}
                          	
                          	r->cur.pos =  0;
                          	r->cur.blk =  r->first;
                          	r->cStr =     NULL;
                          	
                          	return r;
                          }
                          
                          
                          
                          String* String_copy(const String* this) {
                          	if(this==NULL)    return NULL;
                          	
                          	if(String_hasCStr(this))
                          		return String_new(this->cStr);
                          	
                          	String* r;
                          	
                          	r =  malloc(sizeof(String));
                          	if(r==NULL) {
                          		errorMessage("malloc", "when allocating a String");
                          		return NULL;
                          	}
                          	
                          	StringBlk *blk =  this->first,
                          	          **p =   &r->first;
                          	r->size =   0;
                          	r->capac =  0;
                          	if(blk==NULL)    r->first =  NULL;
                          	else while(blk!=NULL) {
                          		*p =  StringBlk_copy(blk);
                          		if(*p==NULL) {
                          			errorMessage("StringBlk_copy", "when creating a copy of"
                          			 " a String block");
                          			 String_del(r);
                          			 return NULL;
                          		}
                          		r->capac +=  (*p)->capac;
                          		r->size +=   (*p)->size;
                          		blk =  blk->next;
                          		if(blk!=NULL)    p =  &(*p)->next;
                          	}
                          	r->last =     *p;
                          	(*p)->next =  NULL;
                          	r->cur.pos =  0;
                          	r->cur.blk =  r->first;
                          	r->cStr =     NULL;
                          	
                          	return r;
                          }
                          
                          
                          
                          void String_del(String* this) {
                          	if(this==NULL)    return;
                          	if(String_hasSeparateCStr(this))
                          		free(this->cStr);
                          	if(!String_isNull(this))
                          		StringBlk_del(this->first);
                          	free(this);
                          }
                          
                          
                          
                          
                          
                          /**  SIZE, CAPACITY
                           **    size, capacity, isEmpty, isNull, empty, clear
                           **/
                          
                          
                          
                          inline  size_t String_size(const String* this)
                          	{ return (this!=NULL)? this->size : 0; }
                          
                          
                          
                          inline  size_t String_capacity(const String* this)
                          	{ return (this!=NULL)? this->capac : 0; }
                          
                          
                          
                          inline  int String_isEmpty(const String* this)
                          	{ return (this!=NULL)? !this->size : 1; }
                          
                          
                          
                          inline  int String_isNull(const String* this)
                          	{ return (this!=NULL)? !this->capac : 1; }
                          
                          
                          
                          void String_empty(String* this) {
                          	if(this==NULL)    return;
                          	StringBlk* p =  this->first;
                          	while(p!=NULL) {
                          		*p->p =    '\0';
                          		p->size =  0;
                          		p =        p->next;
                          	}
                          	this->size =  0;
                          }
                          
                          
                          
                          void String_clear(String* this) {
                          	if(this==NULL)    return;
                          	if(String_hasCStr(this)) {
                          		if(String_hasSeparateCStr(this))
                          			free(this->cStr);
                          		this->cStr =  NULL;
                          	}
                          	if(!String_isNull(this)) {
                          		StringBlk_del(this->first);
                          		this->first =  NULL;
                          		this->last =   NULL;
                          	}
                          	this->capac =    0;
                          	this->size =     0;
                          	this->cur.pos =  0;
                          	this->cur.blk =  NULL;
                          }
                          
                          
                          
                          
                          
                          /**  CSTR
                           **    getCStr, delCStr, hasCStr, hasSharedCStr, hasSeparateCStr
                           **/
                          
                          
                          
                          inline  const char* String_getCStr(const String* this)
                          	{ return this->cStr; }
                          
                          
                          
                          void String_delCStr(String* this) {
                          	free(this->cStr);
                          	this->cStr =  NULL;
                          }
                          
                          
                          
                          static inline  int String_hasCStr(const String* this)
                          	{ return this->cStr != NULL; }
                          
                          
                          
                          static inline  int String_hasSharedCStr(const String* this)
                          	{ return this->capac  &&  this->cStr == this->first->p; }
                          
                          
                          
                          static inline  int String_hasSeparateCStr(const String* this)
                          	{ return this->cStr != NULL  &&  this->cStr != this->first->p; }
                          
                          
                          
                          
                          
                          /**  BASIC READ / WRITE
                           **    char, setChar
                           **/
                          
                          
                          
                          char String_char(/*const*/ String* this, size_t i) {
                          	if(this==NULL || i>=this->size)    return '\0';
                          	
                          	if(String_hasCStr(this))    return this->cStr[i];
                          	
                          	if(i < this->cur.pos) {
                          		this->cur.pos =  0;
                          		this->cur.blk =  this->first;
                          	}
                          	while(i >= this->cur.pos + this->cur.blk->size) {
                          		this->cur.pos +=  this->cur.blk->size;
                          		this->cur.blk =   this->cur.blk->next;
                          	}
                          	return this->cur.blk->p[i - this->cur.pos];
                          }
                          
                          
                          
                          
                          
                          void String_setChar(String* this, size_t i, char c) {
                          	if(this==NULL || i>=this->size)    return;
                          	
                          	if(i < this->cur.pos) {
                          		this->cur.pos =  0;
                          		this->cur.blk =  this->first;
                          	}
                          	while(i >= this->cur.pos + this->cur.blk->size) {
                          		this->cur.pos +=  this->cur.blk->size;
                          		this->cur.blk =   this->cur.blk->next;
                          	}
                          	this->cur.blk->p[i-this->cur.pos] =  c;
                          	
                          	if(c=='\0') {
                          		this->size =  i;
                          		this->cur.blk->size =  i - this->cur.pos;
                          		this->last =  this->cur.blk;
                          		if(this->last->next!=NULL) {
                          			StringBlk_del(this->last->next);
                          			this->last->next =  NULL;
                          		}
                          	}
                          	
                          	if(String_hasSeparateCStr(this)) {
                          		this->cStr[i] =  c;
                          		if(c=='\0') {
                          			// cf here: < http://www.siteduzero.com/forum-83-704272-p1-causes-d-echec-de-realloc.html >
                          			char* tmp =  realloc(this->cStr, (i+1)*sizeof(char));
                          			if(tmp==NULL)
                          				String_delCStr(this);
                          			else
                          				this->cStr =  tmp;
                          		}
                          	}
                          }
                          
                          
                          
                          
                          
                          /**  UTILITIES
                           **    cmp, cmpCStr, searchChar, searchLastChar
                           **/
                          
                          
                          
                          int String_cmp(const String* s1, const String* s2) {
                          	if(String_isEmpty(s1))    return !String_isEmpty(s2);
                          	if(String_isEmpty(s2))    return -1;
                          	
                          	if(String_hasCStr(s1) && String_hasCStr(s2))
                          		return strcmp(s1->cStr, s2->cStr);
                          	
                          	StringBlk *b1 =  s1->first,
                          	          *b2 =  s2->first;
                          	size_t i1 =  0,
                          	       i2 =  0;
                          	
                          	while(b1->p[i1] == b2->p[i2]) {
                          		if(b1->p[i1]=='\0')    return 0;
                          		++i1;    ++i2;
                          		while(i1 >= b1->size) {    /* while and not if, since there can
                          		                             be empty blocks */
                          			i1 =  0;
                          			b1 =  b1->next;
                          			if(b1==NULL)    return s1->size < s2->size;
                          		}
                          		while(i2 >= b2->size) {
                          			i2 =  0;
                          			b2 =  b2->next;
                          			if(b2==NULL)    return -(s1->size > s2->size);
                          		}
                          	}
                          	return  (b1->p[i1] > b2->p[i2])?  -1 : 1;
                          }
                          
                          
                          
                          int String_cmpCStr(const String* this, const char* s) {
                          	if(String_isEmpty(this))    return s!=NULL && *s!='\0';
                          	if(s==NULL || *s=='\0')     return -1;
                          	
                          	if(String_hasCStr(this))
                          		return strcmp(this->cStr, s);
                          	
                          	StringBlk *blk =  this->first;
                          	size_t i =  0,
                          	       j =  0;
                          	
                          	while(blk->p[i] == s[j]) {
                          		if(s[j]=='\0')    return 0;
                          		++i;    ++j;
                          		while(i >= blk->size) {    /* while and not if, since there can
                          		                             be empty blocks */
                          			i =    0;
                          			blk =  blk->next;
                          			if(blk==NULL)    return s[j]!='\0';
                          		}
                          	}
                          	return  (blk->p[i] > s[j])?  -1 : 1;
                          }
                          
                          
                          
                          ptrdiff_t String_searchChar(const String* this, char c) {
                          	if(String_isEmpty(this))    return (c=='\0')? 0 : -1;
                          	if(c=='\0')                 return this->size;
                          	
                          	if(String_hasCStr(this))    return strchr(this->cStr, c) - this->cStr;
                          	
                          	StringBlk* blk =  this->first;
                          	size_t pos =      0;
                          	char* r;
                          	while(blk!=NULL) {
                          		r =  strchr(blk->p, c);
                          		if(r!=NULL)    return r - blk->p + pos;
                          		pos +=  blk->size;
                          		blk =   blk->next;
                          	}
                          	return -1;
                          }
                          
                          
                          
                          ptrdiff_t String_searchLastChar(const String* this, char c) {
                          	if(String_isEmpty(this))    return (c=='\0')? 0 : -1;
                          	if(c=='\0')                 return this->size;
                          	
                          	if(String_hasCStr(this))    return strrchr(this->cStr, c) - this->cStr;
                          	
                          	StringBlk* blk =   this->first;
                          	size_t pos =       0;
                          	char* r;
                          	ptrdiff_t found =  -1;
                          	while(blk!=NULL) {
                          		r =  strrchr(blk->p, c);
                          		if(r!=NULL)    return found =  r - blk->p + pos;
                          		pos +=  blk->size;
                          		blk =   blk->next;
                          	}
                          	return found;
                          }
                          
                          
                          
                          
                          
                          /**  INSERTION
                           **    concat
                           **/
                          
                          
                          
                          String* String_concat(String* this, const String* s2) {
                          	if(this==NULL)    return NULL;
                          	if(s2==NULL)      return this;
                          	
                          	String* cpy =  String_copy(s2);
                          	if(cpy==NULL) {
                          		errorMessage("String_copy", "when copying a String to add it to"
                          		 " the end of another one");
                          		return NULL;
                          	}
                          	
                          	if(String_isNull(this))    this->first =       cpy->first;
                          	else                       this->last->next =  cpy->first;
                          	this->last =    cpy->last;
                          	this->capac +=  cpy->capac;
                          	this->size +=   cpy->size;
                          	/*  /!\  UPDATE CSTR DATAS */
                          	
                          	free(cpy);
                          	return this;
                          }
                          
                          
                          
                          
                          
                          /**  IN / OUT
                           **    fput, put
                           **/
                          
                          
                          
                          void String_fput(const String* this, FILE* stream) {
                          	if(this==NULL || stream==NULL)    return;
                          	StringBlk* p =  this->first;
                          	while(p!=NULL) {
                          		fputs(p->p, stream);
                          		p =  p->next;
                          	}
                          }
                          
                          
                          
                          inline  void String_put(const String* this)
                          	{ String_fput(this, stdout); }
                          



                          N'hésitez pas à commenter, tester, rapporter les erreurs… Moi-même, je n'ai pas effectué de tests très poussés.
                          • Partager sur Facebook
                          • Partager sur Twitter
                            3 novembre 2011 à 10:20:25

                            Salut,

                            Juste pour dire, il manque l'implémentation de la fonction String_cStr.
                            Sinon, il y a une chose qui me choque en regardant les différentes fonctions de l'interface, c'est qu'il n'y a pas de fonction permettant d'ajouter une chaîne sous forme d'un simple char*. On est obliger de crée un nouvel objet String et puis seulement d'appeler String_concat.

                            Du point de vue du code, il me semble qu'il y a un petit problème avec l'utilisation du membre cStr. Si j'ai bien saisit, le but est de conserver le tableau alloué par la fonction String_cStr. Or, ce pointeur n'est rien d'autre que le contenu de la chaîne à un moment donné. En effet, si je fait appel à String_concat par après, ce tableau ne sera en rien modifié. Il ne me semble donc pas correcte de se baser dessus pour simplifier la fonction String_copy:

                            if(String_hasCStr(this))
                            	return String_new(this->cStr);
                            


                            De même pour la fonction String_char, on pourrait appeler la fonction String_setChar avant et ainsi avoir un contenu différent dans les blocs et dans le tableau.
                            Enfin, il me semble que tu n'utilises pas la fonction String_newEmpty ;)
                            • Partager sur Facebook
                            • Partager sur Twitter
                              4 novembre 2011 à 19:10:43

                              Merci pour tes remarques (et ta lecture).

                              Concernant String_cStr, je ne l'ai tout simplement pas encore codée. Idem pour String_concatCStr (concaténation avec un deuxième argument de type const char*) et plusieurs autres fonctions d'insertion (insertion en milieu ou en début, avec variantes prenant en paramètre un char, un const char* ou un const String*).

                              Concernant cette histoire de cStr, si ce membre vaut NULL, alors il n'y a pas de contenu alloué, parce que String_cStr n'a jamais été appelé ou que le contenu a été supprimé depuis. Sinon, le contenu de ce membre est mis à jour lors d'une modification, à moins qu'il ne soit simplement supprimé (si l'opération de mise à jour devient trop coûteuse, concaténation par exemple). cf par exemple cette portion de String_setChar :
                               	if(String_hasSeparateCStr(this)) {
                              		this->cStr[i] =  c;
                              		if(c=='\0') {
                              			// cf here: < http://www.siteduzero.com/forum-83-704272-p1-causes-d-echec-de-realloc.html >
                              			char* tmp =  realloc(this->cStr, (i+1)*sizeof(char));
                              			if(tmp==NULL)
                              				String_delCStr(this);
                              			else
                              				this->cStr =  tmp;
                              		}
                              	}
                              
                              Il est vrai que dans String_concat, je ne l'ai pas encore mis en place (accès soudain de flemme sans doute).

                              Pour String_newEmpty, c'est vrai qu'actuellement elle n'est pas employée, mais peut-être, dans un usage futur… Et puis de toute façon, l'utilisateur peut s'en servir, elle fait partie des fonctions de l'interface (même s'il n'est pas sensé savoir comment est implémenté String, il est au courant qu'il y a une capacité >= taille).
                              • Partager sur Facebook
                              • Partager sur Twitter
                                21 février 2012 à 19:14:55

                                Bonjour,

                                Je continue de compléter ce projet (et de refondre l’organisation de base à tout va). Je pensais implémenter une sorte de “copy on write”, c’est-à-dire une optimisation qui fait que le contenu d’une chaîne n’est pas copiée tout de suite (lors d’une copie, insertion, concaténation…), mais seulement lorsqu’il y en a besoin parce qu’on effectue une écriture sur ce contenu. Faut donc que je revoie la structure de base.
                                Ce que je vais sans doute faire, c’est séparer le contenu de la chaîne, autrement dit quelque chose de ce genre :
                                /* Contenu */
                                struct StringContent {
                                    unsigned int nRefs;    /* nombre de références (nombre de StringLink qui pointent ici) */
                                    …    /* diverses informations (taille, etc.) */
                                    char buf[];    /* le contenu lui-même */
                                };
                                
                                /* Maillon de liste chaînée de contenu */
                                struct StringLink {
                                    struct StringLink* next;
                                    struct StringContent* content;
                                };
                                
                                struct String {
                                    struct StringLink* first;    /* chaîne de contenu */
                                    …    /* diverses informations (taille, etc.) */
                                };
                                


                                Le problème, c’est que ça multiplie les entités devant être allouées une par une, avec autant de déréférencements à la clef (String, maillon de la chaîne, puis contenu). De plus, lors d’une copie pas-vraiment-copiée (juste rajout d’une référence), il faut quand même au final allouer et initialiser la chaîne de StringLink. C’est moins lourd que de tout copier, mais ça restreint quand même le gain. Si vous voyez une autre solution, je suis preneur.
                                Ne pas séparer le contenu de la chaîne me semble plus compliqué. Certes, ça parait sensé parce que lors d’une copie on conserve une chaîne identique, mais il faudrait rajouter et tenir à jour un membre `nRefs` pour chaque maillon ; et si jamais on modifie le contenu d’un maillon, il faudra copier réellement tous les maillons qui suivent (donc moins économe / sélectif).

                                Note : Pour ne pas avoir une 4è entité, je compte utiliser pour le contenu un flexible array member (6.2.7.1, §16 — je ne connais pas la traduction française). Et aussi, peut-être, économiser une allocation supplémentaire en faisant plutôt ceci pour String :
                                struct String {
                                    struct StringLink first;    /* chaîne de contenu : premier maillon directement inclus et non pointé */
                                    …    /* diverses informations (taille, etc.) */
                                };
                                
                                Mais ça complique les algorithmes (faut-il faire free() sur ce bloc ou pas ?).
                                • Partager sur Facebook
                                • Partager sur Twitter

                                Une implémentation de String en C

                                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                • Editeur
                                • Markdown