Pourquoi des chaînes à zéro terminal? Ou: stockage terminé par des caractères nuls vs. caractères + longueur

J’écris un interpréteur de langue en C, et mon type de ssortingng contient un atsortingbut length , comme ceci:

 struct Ssortingng { char* characters; size_t length; }; 

Pour cette raison, je dois passer beaucoup de temps sur mon interprète à traiter ce type de chaîne manuellement, car C n’inclut pas la prise en charge intégrée. J’ai envisagé de passer à de simples chaînes à zéro terminal uniquement pour se conformer au C sous-jacent, mais il semble y avoir beaucoup de raisons de ne pas:

La vérification des limites est intégrée si vous utilisez “longueur” au lieu de chercher une valeur nulle.

Vous devez traverser la chaîne entière pour trouver sa longueur.

Vous devez faire des choses supplémentaires pour gérer un caractère null au milieu d’une chaîne terminée par un zéro.

Les chaînes nulles se terminent mal avec Unicode.

Les chaînes non terminées par un zéro peuvent interner davantage, c’est-à-dire que les caractères pour “Hello, world” et “Hello” peuvent être stockés au même endroit, mais avec des longueurs différentes. Cela ne peut pas être fait avec des chaînes à zéro terminal.

Slice de chaîne (note: les chaînes sont immuables dans ma langue). Évidemment, la seconde est plus lente (et plus sujette aux erreurs: pensez à append une vérification des erreurs de begin et de end aux deux fonctions).

 struct Ssortingng slice(struct Ssortingng in, size_t begin, size_t end) { struct Ssortingng out; out.characters = in.characters + begin; out.length = end - begin; return out; } char* slice(char* in, size_t begin, size_t end) { char* out = malloc(end - begin + 1); for(int i = 0; i < end - begin; i++) out[i] = in[i + begin]; out[end - begin] = '\0'; return out; } 

Après tout cela, ma reflection n’est plus de savoir si je devrais utiliser des chaînes de caractères terminées par un zéro: je réfléchis à la raison pour laquelle C les utilise!

Ma question est donc la suivante: y at-il des avantages à la null-résiliation qui me manque?

    La solution habituelle consiste à faire les deux: conserver la longueur et conserver le terminateur nul. Ce n’est pas beaucoup de travail supplémentaire et signifie que vous êtes toujours prêt à passer la chaîne à n’importe quelle fonction.

    Les chaînes nuls-terminées pèsent souvent sur les performances, pour la raison évidente que le temps mis à découvrir la longueur dépend de la longueur. Du côté positif, ils représentent le moyen standard de représenter les chaînes en C, vous n’avez donc pas d’autre choix que de les prendre en charge si vous souhaitez utiliser la plupart des bibliothèques C.

    De retour à l’essentiel de Joel:

    Pourquoi les chaînes C fonctionnent-elles de cette façon? C’est parce que le microprocesseur PDP-7, sur lequel UNIX et le langage de programmation C ont été inventés, avait un type de chaîne ASCIZ. ASCIZ signifiait “ASCII avec un Z (zéro) à la fin”.

    Est-ce la seule façon de stocker des chaînes? Non, en fait, c’est l’une des pires façons de stocker des chaînes. Pour les programmes non sortingviaux, les API, les systèmes d’exploitation et les bibliothèques de classes, évitez les chaînes ASCIZ telles que peste.

    Un des avantages est que, avec la terminaison null, la fin d’une chaîne terminée par un zéro est également une chaîne terminée par un zéro. Si vous avez besoin de passer une sous-chaîne commençant par le Nième caractère (à condition qu’il n’y ait pas de saturation de mémoire tampon) dans une fonction de traitement de chaîne – pas de problème, transmettez-y l’adresse non utilisée. Lorsque vous stockez la taille d’une autre manière, vous devez créer une nouvelle chaîne.

    L’un des avantages des chaînes nulles est que si vous parcourez une chaîne caractère par caractère, il vous suffit de conserver un seul pointeur pour adresser la chaîne:

     while (*s) { *s = toupper(*s); s++; } 

    alors que pour les chaînes sans sentinelles, vous devez conserver deux bits d’état: soit un pointeur et un index:

     while (i < s.length) { s.data[i] = toupper(s.data[i]); i++; } 

    ... ou un pointeur actuel et une limite:

     s_end = s + length; while (s < s_end) { *s = toupper(*s); s++; } 

    Lorsque les registres de processeurs constituaient une ressource rare (et que les compilateurs les affectaient plus difficilement), cela était important. Maintenant, pas tellement.

    Légèrement décalé, mais il existe un moyen plus efficace de créer des chaînes avec un préfixe en longueur que la façon dont vous le décrivez. Créez une structure comme celle-ci (valide à partir de C99):

     struct Ssortingng { size_t length; char characters[0]; } 

    Cela crée une structure qui a la longueur au début, avec l’élément ‘caractères’ utilisable en tant que char * exactement comme vous le feriez avec votre structure actuelle. La différence, toutefois, est que vous ne pouvez affecter qu’un seul élément sur le tas pour chaque chaîne, au lieu de deux. Allouez vos chaînes comme ceci:

     mystr = malloc(sizeof(Ssortingng) + strlen(cssortingng)) 

    Par exemple, la longueur de la structure (qui est simplement le size_t) plus un espace suffisant pour y placer la chaîne réelle.

    Si vous ne souhaitez pas utiliser C99, vous pouvez également le faire avec “caractères” [1] “et soustraire 1 de la longueur de la chaîne à allouer.

    Les longueurs ont aussi leurs problèmes.

    • La longueur nécessite un espace de stockage supplémentaire (ce n’est pas un problème pour le moment, mais un facteur important il y a 30 ans).

    • Chaque fois que vous modifiez une chaîne, vous devez mettre à jour la longueur afin de réduire les performances.

    • Avec une chaîne terminée par NUL, vous pouvez toujours utiliser une longueur ou stocker un pointeur sur le dernier caractère. Ainsi, si vous effectuez de nombreuses manipulations de chaîne, vous pouvez toujours égaler les performances de chaîne-avec-longueur.

    • Les chaînes terminées par NUL sont beaucoup plus simples – Le terminateur NUL est simplement une convention utilisée par des méthodes telles que strcat pour déterminer la fin de la chaîne. Ainsi, vous pouvez les stocker dans un tableau de caractères normal plutôt que d’avoir à utiliser une structure.

    Juste en jetant quelques hypothèses:

    • il n’y a aucun moyen d’obtenir une “mauvaise” implémentation de chaînes terminées par null. Une structure standardisée pourrait toutefois avoir des implémentations spécifiques au fournisseur.
    • aucune structure n’est requirejse. Les chaînes à terminaison nulle sont “intégrées” pour ainsi dire, en vertu du fait qu’elles constituent un cas spécial de caractère *.

    Bien que je préfère la méthode array + len dans la plupart des cas, il existe des raisons valables pour utiliser la terminaison null.

    Prenons un système 32 bits.

    Pour stocker une chaîne de 7 octets
    char * + size_t + 8 octets = 19 octets

    Pour stocker une chaîne vide de 7 octets
    char * + 8 = 16 octets.

    Les tableaux de termes nuls n’ont pas besoin d’être immuables comme le font vos chaînes. Je peux heureusement tronquer la chaîne de caractères en plaçant simplement un caractère nul. Si vous codez, vous devrez créer une nouvelle chaîne impliquant l’allocation de mémoire.

    Selon l’utilisation des chaînes, vos chaînes ne pourront jamais égaler les performances possibles avec les c-ssortingngs par opposition à vos chaînes.

    Vous avez absolument raison de dire que la terminaison 0 est une méthode médiocre en ce qui concerne la vérification de type et les performances pour une partie des opérations. Les réponses sur cette page résument déjà les origines et les utilisations correspondantes.

    J’ai aimé la façon dont Delphi stockait les chaînes. Je crois qu’il maintient une longueur / maxlength avant la chaîne (longueur variable). De cette façon, les chaînes peuvent être terminées par un caractère null pour des raisons de compatibilité.

    Ce qui me préoccupe avec votre mécanisme: – pointeur supplémentaire – immuabilité si dans les parties fondamentales de votre langue; Normalement, les types de cordes ne sont pas immuables, donc si vous réfléchissez un jour, ce sera difficile. Vous auriez besoin de mettre en œuvre un mécanisme de «création de copie sur changement» – utilisation de malloc (peu efficace, mais peut être inclus ici simplement pour vous faciliter la tâche?)

    Bonne chance; écrire votre propre interprète peut être très instructif pour comprendre principalement la grammaire et la syntaxe des langages de programmation! (du moins, c’était pour moi)

    Je pense que la raison principale est que la norme ne dit rien de concret sur la taille d’aucun type que char. Mais sizeof (char) = 1 et ce n’est certainement pas suffisant pour la taille de la chaîne.