Quels sont les problèmes d’une chaîne terminée par zéro que les chaînes avec préfixes de longueur résolvent?

Quels sont les problèmes d’une chaîne terminée par zéro que les chaînes avec préfixes de longueur résolvent?

Je lisais le livre Write Great Code vol. 1 et j’avais cette question à l’esprit.

Un problème est qu’avec les chaînes terminées par zéro, vous devez continuer à rechercher la fin de la chaîne à plusieurs resockets. L’exemple classique où cela est inefficace est la concaténation dans un tampon:

 char buf[1024] = "first"; strcat(buf, "second"); strcat(buf, "third"); strcat(buf, "fourth"); 

À chaque appel à strcat le programme doit commencer au début de la chaîne et trouver le terminateur pour savoir par où commencer l’ajout. Cela signifie que la fonction passe de plus en plus de temps à trouver le lieu à append à mesure que la chaîne s’allonge.

Avec une chaîne préfixée en longueur, l’équivalent de la fonction strcat saurait où se trouve immédiatement la fin et mettrait simplement à jour la longueur après l’avoir ajoutée.

Chaque manière de représenter des chaînes de caractères comporte des avantages et des inconvénients. Leur dépendra de ce que vous faites avec les chaînes de caractères et des opérations qui doivent être efficaces. Le problème décrit ci-dessus peut être résolu en gardant manuellement le suivi de la fin de la chaîne au fur et à mesure de sa croissance. Ainsi, en modifiant le code, vous éviterez les coûts de performances.

Un problème est que vous ne pouvez pas stocker des caractères nuls (valeur zéro) dans une chaîne terminée par zéro. Cela rend impossible le stockage de certains codages de caractères ainsi que des données cryptées.

Les chaînes préfixées en longueur ne souffrent pas de cette limitation.

Premièrement, une clarification: les chaînes C ++ (c.-à-d. std::ssortingng ) ne sont pas obligées de se terminer par zéro avant C ++ 11 . Cependant, ils ont toujours fourni un access à une chaîne C à zéro terminal.

Les chaînes de style C se terminent par un caractère 0 pour des raisons historiques .

Les problèmes auxquels vous faites référence sont principalement liés à des problèmes de sécurité: les chaînes zéro-terminées doivent avoir un terminateur zéro. S’ils en manquent (pour quelque raison que ce soit), la longueur de la chaîne devient incertaine et peut entraîner des problèmes de saturation de la mémoire tampon (qu’un attaquant malveillant peut exploiter en écrivant des données arbitraires à des endroits où elle ne devrait pas se trouver .. DEP consortingbue à atténuer ces problèmes. mais c’est hors sujet ici).

Il est le mieux résumé dans L’erreur la plus chère à un octet de Poul-Henning Kamp.

  1. Coûts liés aux performances: il est moins coûteux de manipuler la mémoire par fragments, ce qui est impossible si vous devez toujours rechercher le caractère NULL. En d’autres termes, si vous savez à l’avance que vous avez une chaîne de 129 caractères, il serait probablement plus efficace de la manipuler en sections de 64, 64 et 1 octets, au lieu d’un caractère à la fois.
  2. Sécurité: Marco A. a déjà frappé fort. Le dépassement et le sous-fonctionnement des tampons de chaînes constituent toujours une voie majeure pour les attaques de pirates.

  3. Coûts de développement du compilateur: Des coûts importants sont associés à l’optimisation des compilateurs pour les chaînes de terminaison nul qui auraient été plus faciles avec le format d’adresse et de longueur.

  4. Coûts de développement du matériel: les coûts de développement du matériel sont également élevés pour les instructions spécifiques aux chaînes associées aux chaînes de terminaison null.

Quelques fonctionnalités supplémentaires pouvant être implémentées avec des chaînes préfixées en longueur:

  1. Il est possible d’avoir plusieurs styles de préfixe de longueur, identifiables par un ou plusieurs bits du premier octet identifiés par le pointeur / la référence de chaîne. En échange d’un peu de temps supplémentaire pour déterminer la longueur de la chaîne, on pourrait par exemple utiliser un préfixe à un octet pour les chaînes courtes et un préfixe plus long pour les chaînes plus longues. Si vous utilisez un grand nombre de chaînes de 1 à 3 octets, vous pouvez économiser plus de 50% sur la consommation de mémoire globale de ces chaînes par rapport à l’utilisation d’un préfixe fixe de 4 octets; un tel format pourrait également accueillir des chaînes dont la longueur dépasse la plage des entiers 32 bits.

  2. Il est possible de stocker des chaînes de longueur variable dans des mémoires tampons vérifiées par des bornes à un coût de seulement un ou deux bits dans le préfixe de longueur. Le nombre N combiné aux autres bits indiquerait l’une des trois choses suivantes:

    1. Une chaîne de N octets

    2. (Facultatif) Un tampon de N octets contenant une chaîne de longueur nulle

    3. Un tampon de N octets qui, si son dernier octet B est inférieur à 248, contient une chaîne de longueur NB-1; si 248 ou plus, les B-247 octets précédents stockent la différence entre la taille de la mémoire tampon et la longueur de la chaîne. Notez que si la longueur de la chaîne est précisément N-1, la chaîne sera suivie d’un octet NUL, et si elle est inférieure à celle-ci, l’octet suivant la chaîne sera inutilisé et pourra être défini sur NUL.

    Avec une telle approche, il faudrait initialiser les mémoires tampons fortes avant utilisation (pour indiquer leur longueur), mais il ne serait alors plus nécessaire de passer la longueur d’un tampon de chaîne à une routine qui allait y stocker des données.

  3. On peut utiliser certaines valeurs de préfixe pour indiquer diverses choses spéciales. Par exemple, on peut avoir un préfixe qui indique qu’il n’est pas suivi d’une chaîne, mais plutôt d’un pointeur de données de chaîne et de deux entiers donnant la taille du tampon et la longueur actuelle. Si les méthodes qui fonctionnent sur des chaînes appellent une méthode pour obtenir le pointeur de données, la taille de la mémoire tampon et la longueur, on peut transmettre à une telle méthode une référence à une partie d’une chaîne à un prix avantageux, à condition que la chaîne elle-même survivra à l’appel de la méthode.

  4. On peut étendre la fonctionnalité ci-dessus avec un bit pour indiquer que les données de chaîne se trouvent dans une région générée par malloc et peuvent être redimensionnées si nécessaire; En outre, des méthodes sûres peuvent parfois renvoyer une chaîne générée dynamicment allouée sur le tas, parfois une chaîne immuable, et demander au destinataire d’effectuer une opération “libérer cette chaîne si elle n’est pas statique”.

Je ne sais pas si les implémentations de chaînes préfixées implémentent toutes ces fonctionnalités supplémentaires, mais elles peuvent toutes être gérées à très peu de frais en espace de stockage, relativement peu en code, et à un coût moindre en temps que celui nécessaire pour utiliser NUL- chaînes terminées dont la longueur n’était ni connue ni courte.

Quels sont les problèmes d’une chaîne terminée par zéro que les chaînes avec préfixes de longueur résolvent?

Pas du tout.
C’est juste des bonbons pour les yeux.

Les chaînes préfixées en longueur ont, dans le cadre de leur structure, des informations sur la longueur de la chaîne. Si vous voulez faire la même chose avec des chaînes à zéro terminal, vous pouvez utiliser une variable d’assistance;

 lpssortingng = "foobar"; // saves '6' somewhere "inside" lpssortingng ztssortingng = "foobar"; ztlength = 6; // saves '6' in a helper variable 

De nombreuses fonctions de la bibliothèque C fonctionnent avec des chaînes terminées par un zéro et ne peuvent rien utiliser au-delà de l’octet '\0' . C’est un problème avec les fonctions elles-mêmes, pas avec la structure de chaîne. Si vous avez besoin de fonctions traitant des chaînes à zéro terminal avec zéros incorporés, écrivez les vôtres.