Просмотр исходного кода

docs: string-implementation and UTF-8 handling research notes

Eric Streit 3 недель назад
Родитель
Сommit
0ed2862113
2 измененных файлов с 146 добавлено и 0 удалено
  1. 126 0
      docs/strings.txt
  2. 20 0
      docs/utf8-systems.txt

+ 126 - 0
docs/strings.txt

@@ -0,0 +1,126 @@
+Les implémentations standard des chaînes varient, mais la plupart utilisent une approche std::string avec Small String Optimization (SSO) ou des variantes de stockage sur le tas; GCC/libstdc++, MSVC et Clang/libc++ adoptent des sizes différents (généralement 24–32 octets) et utilisent SSO pour stocker les petites chaînes en ligne sans allocation, tout en passant à l’allocation dynamique pour les chaînes plus longues.
+Réponse détaillée : common string implementation in compilers
+Réflexion terminée
+Recherche web terminée
+
+Voici une réponse détaillée sur les implémentations courantes des chaînes de caractères dans les compilateurs modernes, en se basant sur les pratiques standard des bibliothèques standard (libstdc++, libc++, MSVC STL) et des compilateurs (GCC, Clang, MSVC).
+1. Représentation mémoire des chaînes
+
+Les chaînes de caractères sont généralement implémentées sous forme d’objets encapsulant :
+
+    Un pointeur vers un buffer de caractères (alloué dynamiquement ou en SSO).
+    Une taille (nombre de caractères, sans le \0 terminal).
+    Une capacité (taille maximale du buffer alloué, utile pour les opérations de modification).
+
+Cette structure permet d’éviter les recopies inutiles et d’optimiser les performances.
+2. Small String Optimization (SSO)
+
+La SSO est une optimisation majeure utilisée par la plupart des implémentations modernes. Elle consiste à stocker les petites chaînes directement dans l’objet std::string lui-même, sans allocation dynamique sur le tas. Cela réduit la fragmentation mémoire et améliore les performances pour les chaînes courantes.
+Taille des buffers SSO par compilateur
+Compilateur/Bibliothèque	Taille du buffer SSO (en octets)	Exemple de chaîne maximale en SSO
+libstdc++ (GCC)	15 octets	15 caractères (16 avec \0)
+libc++ (Clang)	22 octets	22 caractères (23 avec \0)
+MSVC STL	15 octets	15 caractères (16 avec \0)
+
+    Exemple : Une chaîne de 10 caractères sera stockée directement dans l’objet std::string sans allocation dynamique.
+    Seuil : Au-delà de la taille du buffer SSO, une allocation dynamique est utilisée.
+
+3. Structure interne des std::string
+a) libstdc++ (GCC)
+
+    Taille de l’objet : 32 octets (sur 64 bits).
+    Structure :
+        8 octets pour le pointeur (_M_data).
+        8 octets pour la taille (_M_length).
+        8 octets pour la capacité (_M_capacity).
+        8 octets réservés (alignement ou métadonnées supplémentaires).
+    SSO : Les 15 premiers octets du buffer sont utilisés pour stocker la chaîne directement dans l’objet.
+
+b) libc++ (Clang)
+
+    Taille de l’objet : 24 ou 32 octets (selon la plateforme).
+    Structure :
+        8 octets pour le pointeur (__r_).
+        8 octets pour la taille (__size_).
+        8 octets pour la capacité (__cap_).
+    SSO : Les 22 premiers octets du buffer sont utilisés pour stocker la chaîne directement dans l’objet.
+
+c) MSVC STL
+
+    Taille de l’objet : 32 octets (sur 64 bits).
+    Structure :
+        8 octets pour le pointeur (_Bx._Ptr).
+        8 octets pour la taille (_Mysize).
+        8 octets pour la capacité (_Myres).
+        8 octets réservés (alignement).
+    SSO : Les 15 premiers octets du buffer sont utilisés pour stocker la chaîne directement dans l’objet.
+
+4. Gestion de la mémoire dynamique
+
+    Allocation : Lorsque la taille de la chaîne dépasse le buffer SSO, une allocation dynamique est effectuée sur le tas.
+    Réallocation : Si la chaîne est modifiée et que sa taille dépasse la capacité actuelle, une nouvelle allocation est effectuée, et les données sont copiées.
+    Optimisations :
+        COW (Copy-On-Write) : Ancienne technique abandonnée (pour des raisons de thread-safety et de performances).
+        Move semantics : Utilisation de std::move pour transférer la propriété des données sans recopie.
+
+5. Comparaison des performances
+Critère	libstdc++ (GCC)	libc++ (Clang)	MSVC STL
+Taille de l’objet	32 octets	24–32 octets	32 octets
+Taille SSO	15 octets	22 octets	15 octets
+Allocation dynamique	Oui	Oui	Oui
+Move semantics	Oui	Oui	Oui
+Thread-safety	Oui	Oui	Oui
+6. Exemple de code et comportement
+
+cpp
+Copier
+#include <string>
+#include <iostream>
+
+int main() {
+    std::string s1 = "Hello"; // Stocke dans le buffer SSO (5 < 15/22)
+    std::string s2 = "This is a very long string that exceeds the SSO buffer size";
+    // s2 utilise une allocation dynamique
+
+    std::cout << "Size of std::string: " << sizeof(std::string) << " bytes\n";
+    std::cout << "SSO threshold: ~15 or 22 bytes\n";
+    return 0;
+}
+
+    Sortie typique :
+
+    Size of std::string: 32 bytes
+    SSO threshold: ~15 or 22 bytes
+
+7. Évolution et alternatives
+
+    C++11 et ultérieur : Introduction de std::string_view pour éviter les copies inutiles.
+    Bibliothèques tierces :
+        folly::fbstring (Facebook) : Optimisations supplémentaires pour les très grandes chaînes.
+        absl::string_view (Google) : Vue sur une chaîne sans allocation.
+        boost::string_ref : Alternative légère pour éviter les copies.
+
+8. Cas particuliers
+
+    Chaînes vides : Généralement représentées par un pointeur nullptr ou un buffer SSO vide.
+    Chaînes avec \0 : std::string peut contenir des \0 internes (contrairement à char*), car la taille est stockée séparément.
+    Internationalisation : Les chaînes UTF-8 sont gérées comme des séquences d’octets, sans interprétation particulière.
+
+9. Outils pour inspecter l’implémentation
+
+    Compiler Explorer (godbolt.org) : Permet de voir l’assembleur généré et d’inspecter la taille de std::string.
+    Débogueurs : Utiliser p sizeof(std::string) dans GDB ou LLDB pour afficher la taille de l’objet.
+    Bibliothèques de débogage : libc++abi ou libstdc++-dbg pour inspecter les structures internes.
+
+10. Résumé des bonnes pratiques
+
+    Privilégier std::string pour les chaînes modifiables.
+    Éviter les copies inutiles avec std::move ou std::string_view.
+    Connaître le seuil SSO pour optimiser les performances.
+    Utiliser reserve() pour éviter les réallocations fréquentes.
+    Éviter c_str() si la chaîne est modifiée ensuite (car cela peut invalider le pointeur).
+
+Si vous souhaitez des détails sur une implémentation spécifique (par exemple, comment GCC gère la réallocation ou comment MSVC optimise les petites chaînes), précisez votre demande.
+
+
+

+ 20 - 0
docs/utf8-systems.txt

@@ -0,0 +1,20 @@
+how do compilers deal with utf-8 encoded source programs
++ Thought: 9.1s
+Compilers handle UTF-8 at a few distinct stages:
+1. Encoding detection
+The compiler must decide what the file actually is. Common strategies:
+Assume UTF-8 by default (Rust, Go, modern C/C++ toolchains).
+Check for a BOM and/or honor a flag like GCC's -finput-charset=utf-8.
+With no BOM/flag, some pass raw bytes through unvalidated rather than guessing (C/C++).
+2. Lexing/tokenization
+Since UTF-8 is self-synchronizing, the lexer can classify bytes by their high bits: ASCII (< 0x80), continuation bytes (10xxxxxx), or lead bytes (110/1110/11110xxx). This lets it safely skip a whole multi-byte character without mistaking its continuation bytes for punctuation or whitespace. The lexer either validates UTF-8 eagerly or just checks that string literals are well-formed.
+3. Identifiers
+Language-dependent:
+Most languages (C, C++) restrict identifiers to ASCII, so a non-ASCII byte in an identifier position is simply an error.
+Others (Rust, Swift, Go) allow Unicode letters in identifiers and just treat any non-ASCII byte sequence as an identifier character.
+Java reads \uXXXX escapes and applies NFC normalization so canonically-equivalent names collide/dedupe consistently.
+4. String/char literals
+The compiler must transcode the source bytes (UTF-8) into the execution character set and compute widths. This is where sizeof("é"), L"é" (UTF-16/32), u8"…", etc. diverge in C — the value depends on source charset, execution charset, and wchar_t width. Rust instead requires literals to be valid UTF-8 and rejects escapes producing invalid code points.
+5. Diagnostics
+Real problems show up in error reporting: line/column are usually computed in bytes, not graphemes; and when echoing the offending source line, the compiler must avoid truncating a multi-byte character mid-sequence (or escaping invalid bytes) so the terminal doesn't print a broken character.
+The one rule everything hinges on is deferring to UTF-8's self-synchronizing structure: bytes 10xxxxxx are always continuation bytes, so a lexer can skip them blindly and only ever act on ASCII values or lead bytes.