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)
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.
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).
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.
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.
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 voidString_concat(String*,String*);
Dans String_write le fputs ajoute des retour à la ligne non voulus, non?
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?
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 voidString_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.
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
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
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.
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.
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; }
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:
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
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 constchar*) et plusieurs autres fonctions d'insertion (insertion en milieu ou en début, avec variantes prenant en paramètre un char, un constchar* ou un constString*).
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 :
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).
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 ?).
× 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.