Objectif du cours
- la représentation des données en mémoire ;
- les pointeurs ;
- les tableaux ;
- les tailles et capacités de buffers ;
- les allocations dynamiques ;
- la durée de vie des objets ;
- l'ownership ;
- les copies et les réallocations ;
- l'interopérabilité avec les API C, POSIX et Win32 ;
- le parsing de données ;
- les performances.
Code: Select all
const char*
Code: Select all
std::string
Code: Select all
std::string_view
Un ordinateur ne possède pas un type matériel appelé "chaîne de caractères".
La mémoire contient essentiellement des octets.
Lorsque nous écrivons :
Code: Select all
"Hello"
Dans le cas classique d'une chaîne C composée de caractères
Code: Select all
charCode: Select all
Adresse Valeur
0x1000 'H'
0x1001 'e'
0x1002 'l'
0x1003 'l'
0x1004 'o'
0x1005 '\0'
Le dernier caractère,
Code: Select all
'\0'Ainsi, la chaîne visible :
Code: Select all
Hello
Code: Select all
'H' 'e' 'l' 'l' 'o' '\0'
2. LES CHAÎNES C ET LE TERMINATEUR NUL
Une chaîne C est généralement une séquence de caractères terminée par un caractère nul.
Exemple :
Code: Select all
char message[] = "Hello";
Code: Select all
'H' 'e' 'l' 'l' 'o' '\0'
Il peut être écrit :
Code: Select all
'\0'
Code: Select all
'0'
Code: Select all
'0'Le caractère
Code: Select all
'\0'C'est fondamental, car beaucoup de fonctions C ne reçoivent PAS explicitement la longueur de la chaîne.
Par exemple :
Code: Select all
strlen(message);
Elle commence au premier caractère, puis avance jusqu'à rencontrer :
Code: Select all
'\0'
Code: Select all
H -> e -> l -> l -> o -> \0
^
arrêt
3. sizeof() ET strlen() NE MESURENT PAS LA MÊME CHOSE
Considérons :
Code: Select all
char text[] = "Hello";
Donc :
Code: Select all
sizeof(text)
Code: Select all
6
Code: Select all
strlen(text)
Code: Select all
5
Parce que
Code: Select all
strlen()Le
Code: Select all
'\0'Cette distinction est importante :
Code: Select all
sizeof(text) -> taille du tableau en mémoire
strlen(text) -> longueur de la chaîne C
Exemple :
Code: Select all
const char* text = "Hello";
Code: Select all
sizeof(text)
Sur une architecture 64 bits typique :
Code: Select all
sizeof(text) == 8
Le pointeur contient simplement une adresse de 64 bits.
C'est une erreur classique en C/C++.
4. TABLEAU DE char CONTRE POINTEUR const char*
Ces deux déclarations ressemblent beaucoup :
Code: Select all
char a[] = "Hello";
const char* b = "Hello";
Avec :
Code: Select all
char a[] = "Hello";
On peut faire :
Code: Select all
a[0] = 'Y';
Code: Select all
"Yello"
Code: Select all
const char* b = "Hello";
La présence de
Code: Select all
constC'est la bonne manière de manipuler un littéral via un pointeur.
5. UNE CHAÎNE C EST TRÈS PROCHE D'UN POINTEUR
Avec :
Code: Select all
const char* text = "Kernel";
Code: Select all
text
|
v
+---+---+---+---+---+---+---+
| K | e | r | n | e | l |\0 |
+---+---+---+---+---+---+---+
Code: Select all
text[0]
Code: Select all
*(text + 0)
Code: Select all
text[2]
Code: Select all
*(text + 2)
Derrière le texte, on manipule donc réellement des adresses et des zones mémoire.
6. strlen() : CE QUI SE PASSE CONCEPTUELLEMENT
Une version simplifiée pourrait ressembler à :
Code: Select all
std::size_t my_strlen(const char* str)
{
std::size_t length = 0;
while (str[length] != '\0')
{
++length;
}
return length;
}
Considérons :
Code: Select all
char buffer[5] = {
'H',
'e',
'l',
'l',
'o'
};
Code: Select all
'\0'
Code: Select all
strlen(buffer);
Pourquoi ?
Parce que
Code: Select all
strlen()Elle peut inspecter :
Code: Select all
buffer[5]
buffer[6]
buffer[7]
...
Le programme lit donc de la mémoire qui ne fait pas partie du tableau.
Dans le meilleur cas :
le résultat est faux.
Dans un cas plus grave :
le programme plante.
Dans un contexte de sécurité :
cela peut participer à une vulnérabilité.
8. CAPACITÉ DU BUFFER ET LONGUEUR DU TEXTE
Prenons :
Code: Select all
char buffer[64] = "abc";
Code: Select all
64 octets
Code: Select all
3 caractères
- la taille physique du stockage ;
- la quantité de texte actuellement utilisée.
On retrouve exactement la même idée dans :
- les buffers réseau ;
- les fichiers ;
- les structures système ;
- les vecteurs ;
- les paquets ;
- les buffers d'I/O.
Code: Select all
char buffer[4096];
ssize_t bytesRead =
read(fd, buffer, sizeof(buffer));
Code: Select all
sizeof(buffer)
Mais :
Code: Select all
bytesRead
Ce sont deux informations différentes.
9. strcpy() ET LE RISQUE DE BUFFER OVERFLOW
Une fonction historique du C est :
Code: Select all
strcpy()
Code: Select all
char destination[32];
strcpy(destination, "Hello");
Mais :
Code: Select all
char destination[8];
strcpy(
destination,
"Hello this string is much too long"
);
Code: Select all
strcpy()Elle continue à copier les caractères.
Elle peut alors écrire au-delà des huit octets réservés.
Conceptuellement :
Code: Select all
destination
+---+---+---+---+---+---+---+---+
| H | e | l | l | o | | t | h |
+---+---+---+---+---+---+---+---+
|
v
écriture hors du tableau
- écraser une autre variable ;
- corrompre la pile ;
- modifier des métadonnées mémoire ;
- provoquer un crash ;
- ouvrir une faille de sécurité.
10. strcat() ET LA CONCATÉNATION C
Exemple :
Code: Select all
char result[32] = "Hello ";
strcat(result, "World");
Code: Select all
"Hello World"
Code: Select all
longueur actuelle
+
longueur ajoutée
+
1 pour '\0'
11. strcmp() : COMPARER LE CONTENU ET NON LES ADRESSES
Erreur classique :
Code: Select all
const char* a = "hello";
const char* b = "hello";
if (a == b)
{
}
Autrement dit, elle demande conceptuellement :
Code: Select all
adresse(a) == adresse(b) ?
Code: Select all
contenu(a) == contenu(b) ?
Code: Select all
strcmp(a, b)
Code: Select all
if (strcmp(a, b) == 0)
{
// les textes sont identiques
}
12. LES LITTÉRAUX DE CHAÎNES
Une expression comme :
Code: Select all
"Windows"
Le compilateur place les caractères dans une zone adaptée du programme.
On peut l'utiliser directement :
Code: Select all
printf("Hello");
Code: Select all
const char* name = "Windows";
Le code suivant est donc une mauvaise idée :
Code: Select all
char* text =
const_cast<char*>("Hello");
text[0] = 'X';
13. CARACTÈRES D'ÉCHAPPEMENT
Certaines valeurs sont écrites avec une syntaxe spéciale :
Code: Select all
\n
\t
\r
\\
\"
\'
\0
- : nouvelle ligne ;
Code: Select all
\n - : tabulation ;
Code: Select all
\t - : retour chariot ;
Code: Select all
\r - : caractère backslash ;
Code: Select all
\\ - : guillemet ;
Code: Select all
\" - : caractère nul.
Code: Select all
\0
Code: Select all
"C:\\Windows\\System32"
14. RAW STRING LITERALS
C++ permet d'éviter certains échappements grâce aux raw strings.
Syntaxe :
Code: Select all
R"(texte)"
Code: Select all
const char* path =
R"(C:\Windows\System32\drivers)";
Code: Select all
"C:\\Windows\\System32\\drivers"
- les chemins ;
- les expressions régulières ;
- le JSON ;
- le XML ;
- le SQL ;
- les scripts ;
- les textes multilignes.
Code: Select all
const char* config =
R"(
server=localhost
port=8080
debug=true
)";
Code: Select all
\n15. DÉLIMITEURS PERSONNALISÉS DES RAW STRINGS
Le texte suivant :
Code: Select all
)"
Si le contenu lui-même doit contenir cette séquence, on peut utiliser un délimiteur personnel.
Exemple :
Code: Select all
const char* json =
R"JSON(
{
"message": "hello",
"path": "C:\Windows"
}
)JSON";
Code: Select all
JSON16. LES PRÉFIXES DE LITTÉRAUX ET LE DÉVELOPPEMENT WINDOWS
Tu peux rencontrer :
Code: Select all
"hello"
L"hello"
u8"hello"
u"hello"
U"hello"
Code: Select all
L"..."Exemple :
Code: Select all
CreateFileW(
L"C:\\Temp\\data.bin",
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
Code: Select all
CreateFileWLes types Win32 associés sont souvent :
Code: Select all
LPCWSTR
LPWSTR
WCHAR*
17. POURQUOI std::string EXISTE ?
Les chaînes C donnent énormément de contrôle.
Mais elles imposent aussi beaucoup de responsabilités.
Avec un buffer C, il faut souvent gérer :
- la taille ;
- la capacité ;
- le terminateur nul ;
- les allocations ;
- les copies ;
- les concaténations ;
- les réallocations ;
- la destruction.
Code: Select all
std::string
Exemple :
Code: Select all
#include <string>
std::string text{"Hello"};
18. std::string EST UN OBJET PROPRIÉTAIRE
Un
Code: Select all
std::stringExemple :
Code: Select all
std::string text{"Kernel"};
Code: Select all
text
+---------------------------+
| size = 6 |
| capacity = ... |
| stockage -> "Kernel" |
+---------------------------+
C'est le principe RAII.
19. CRÉATION ET AFFECTATION DE std::string
Exemples :
Code: Select all
std::string a;
std::string b{"Windows"};
std::string c{b};
Code: Select all
a = "Linux";
Code: Select all
a = "Windows";
20. size() ET length()
Avec :
Code: Select all
std::string text{"Kernel"};
Code: Select all
text.size()
Code: Select all
text.length()
Contrairement aux chaînes C,
Code: Select all
std::stringIl n'est pas nécessaire de rescanner toute la chaîne avec
Code: Select all
strlen()21. size(), capacity() ET reserve()
Un
Code: Select all
std::stringExemple conceptuel :
Code: Select all
size = 10
capacity = 31
- 10 caractères sont actuellement utilisés ;
- le stockage peut éventuellement accueillir davantage de caractères avant une nouvelle allocation.
Code: Select all
std::string output;
output.reserve(4096);
Code: Select all
reserve(4096)
"la chaîne contient 4096 caractères".
Cela signifie plutôt :
"prépare suffisamment de capacité pour éviter certaines réallocations".
Sa taille logique peut toujours être zéro.
22. resize() N'EST PAS reserve()
Code: Select all
std::string text;
text.resize(100);
La distinction est donc essentielle :
Code: Select all
reserve()
-> modifie la capacité disponible
resize()
-> modifie le nombre logique d'éléments
Exemple :
Code: Select all
std::string first{"Hello"};
std::string second{" World"};
std::string result =
first + second;
Code: Select all
"Hello World"
Code: Select all
first += second;
Code: Select all
first.append(second);
Comme un tableau :
Code: Select all
std::string text{"Hello"};
char c = text[1];
Code: Select all
'e'
Code: Select all
text.at(1)
Code: Select all
at()L'opérateur :
Code: Select all
[]
25. data() : ACCÈS AU BUFFER INTERNE
Une fonction essentielle pour ton profil est :
Code: Select all
text.data()
Exemple :
Code: Select all
std::string payload{"PING"};
send(
socket,
payload.data(),
payload.size(),
0
);
Code: Select all
std::stringCode: Select all
char*- l'adresse du buffer ;
- la taille logique du buffer.
26. c_str() : INTEROPÉRABILITÉ AVEC LES API C
Certaines fonctions demandent explicitement une chaîne C terminée par zéro.
Exemple :
Code: Select all
std::string filename{"data.bin"};
std::FILE* file =
std::fopen(
filename.c_str(),
"rb"
);
Code: Select all
c_str()On retrouve ici la frontière entre deux mondes :
Code: Select all
C++ moderne
std::string
|
| c_str()
v
API C
const char*
27. STOCKAGE CONTIGU DE std::string
Les caractères d'un
Code: Select all
std::stringConceptuellement :
Code: Select all
text.data()
|
v
+---+---+---+---+---+
| H | e | l | l | o |
+---+---+---+---+---+
Mais il faut toujours respecter :
- la durée de vie ;
- la taille ;
- les règles de modification ;
- les éventuelles invalidations.
Considérons :
Code: Select all
std::string text{"Hello"};
const char* ptr =
text.data();
Code: Select all
text +=
" une très longue quantité de texte";
Elle peut alors :
- allouer un nouveau bloc ;
- copier ou déplacer les caractères ;
- libérer l'ancien bloc.
Code: Select all
ptrConceptuellement :
Avant :
Code: Select all
ptr
|
v
[ ancien buffer ]
Code: Select all
text
|
v
[ nouveau buffer ]
ptr
|
v
[ ancien buffer libéré ]
Code: Select all
ptr29. SMALL STRING OPTIMIZATION
De nombreuses implémentations de
Code: Select all
std::stringCode: Select all
Small String Optimization
Code: Select all
SSO
Une petite chaîne peut être stockée directement à l'intérieur de l'objet
Code: Select all
std::stringConceptuellement :
Code: Select all
std::string
+------------------------------+
| metadata | petits caractères |
+------------------------------+
L'avantage est d'éviter une allocation coûteuse pour de petits textes très courants.
Attention :
la taille exacte à partir de laquelle l'implémentation alloue n'est pas garantie par le langage.
Il ne faut jamais écrire du code dépendant d'une valeur précise.
30. COMPARAISON AVEC std::string
Avec les chaînes C :
Code: Select all
a == b
Avec
Code: Select all
std::stringCode: Select all
std::string a{"Linux"};
std::string b{"Linux"};
if (a == b)
{
}
On peut aussi utiliser :
Code: Select all
<
>
<=
>=
31. find() : RECHERCHER UNE SOUS-CHAÎNE
Exemple :
Code: Select all
std::string text{
"Windows Kernel Programming"
};
auto pos =
text.find("Kernel");
Code: Select all
posSinon, la fonction retourne :
Code: Select all
std::string::npos
Code: Select all
if (
text.find("Kernel")
!=
std::string::npos
)
{
// trouvé
}
Code: Select all
nposIl est préférable de l'utiliser explicitement plutôt que d'imaginer un index arbitraire comme
Code: Select all
-1Exemple :
Code: Select all
auto pos =
text.find("missing");
if (pos == std::string::npos)
{
std::println("introuvable");
}
Exemple :
Code: Select all
std::string text{
"Windows Kernel"
};
std::string part =
text.substr(8, 6);
Code: Select all
"Kernel"
Code: Select all
std::string::substr()
Cela signifie qu'une nouvelle représentation du texte est créée.
Il peut donc y avoir une copie et éventuellement une allocation.
Plus tard,
Code: Select all
std::string_view34. replace(), insert() ET erase()
Remplacement :
Code: Select all
std::string text{"Hello Linux"};
text.replace(
6,
5,
"Windows"
);
Code: Select all
std::string text{"Helo"};
text.insert(
3,
"l"
);
Code: Select all
std::string text{
"Hello World"
};
text.erase(
5,
6
);
Insérer ou supprimer au milieu d'un tableau contigu peut nécessiter de déplacer des caractères.
35. starts_with(), ends_with() ET contains()
C++ moderne propose des opérations très lisibles.
Exemple :
Code: Select all
std::string filename{
"driver.sys"
};
if (
filename.ends_with(".sys")
)
{
std::println(
"driver potentiel"
);
}
Code: Select all
if (
path.starts_with(
"C:\\Windows\\"
)
)
{
}
Code: Select all
if (
text.contains("Kernel")
)
{
}
36. clear() ET empty()
Vider une chaîne :
Code: Select all
text.clear();
Code: Select all
if (text.empty())
{
}
Code: Select all
if (text.size() == 0)
{
}
37. ITERER SUR UNE std::string
Une chaîne peut être parcourue comme un conteneur.
Exemple :
Code: Select all
std::string text{
"ABC"
};
for (char c : text)
{
std::println(
"{}",
c
);
}
Code: Select all
A
B
C
Par exemple :
Code: Select all
text += other;
- une vérification de capacité ;
- une allocation ;
- une copie ou un déplacement ;
- une libération de l'ancien stockage.
Code: Select all
substr()
Un :
Code: Select all
insert()
Un développeur système doit donc savoir utiliser les abstractions tout en comprenant leur coût.
39. CONVERTIR UN NOMBRE EN CHAÎNE
Une valeur entière :
Code: Select all
4096
Code: Select all
"4096"
Le premier est une représentation numérique permettant directement des calculs.
Le second est une suite de caractères.
On peut convertir :
Code: Select all
int value{4096};
std::string text =
std::to_string(value);
Code: Select all
"4096"
40. CONVERTIR UNE CHAÎNE EN NOMBRE
C++ fournit notamment :
Code: Select all
std::stoi()
std::stol()
std::stoll()
std::stoul()
std::stoull()
std::stof()
std::stod()
std::stold()
Code: Select all
std::string text{"4096"};
int value =
std::stoi(text);
Code: Select all
value == 4096
Code: Select all
value + 100
Beaucoup d'informations arrivent sous forme textuelle :
- fichiers de configuration ;
- arguments de ligne de commande ;
- /proc sous Linux ;
- protocoles ;
- logs ;
- entrées utilisateur ;
- formats textuels ;
- outils de diagnostic.
Code: Select all
"PID=1337"
Code: Select all
"1337"
42. std::to_chars : CONVERSION DIRECTEMENT DANS UN BUFFER
Pour un code plus bas niveau, C++ propose :
Code: Select all
std::to_chars
Code: Select all
#include <charconv>
#include <array>
std::array<char, 32> buffer{};
int value{12345};
auto result =
std::to_chars(
buffer.data(),
buffer.data()
+ buffer.size(),
value
);
Elle ne nécessite pas automatiquement la création d'un
Code: Select all
std::stringC'est particulièrement intéressant pour :
- les buffers préalloués ;
- le code performant ;
- les protocoles ;
- les systèmes de logs ;
- les parsers.
L'opération inverse :
Code: Select all
std::string_view input{
"4096"
};
int value{};
auto result =
std::from_chars(
input.data(),
input.data()
+ input.size(),
value
);
Code: Select all
adresse début
adresse fin
destination
Aucun terminateur nul n'est nécessaire puisque la borne de fin est fournie explicitement.
44. POINTER + LENGTH : UN MODÈLE FONDAMENTAL
Beaucoup d'interfaces système manipulent les buffers sous cette forme :
Code: Select all
void process(
const void* buffer,
std::size_t length
);
Code: Select all
send(
socket,
data,
size,
flags
);
Code: Select all
adresse + longueur
Code: Select all
std::string_view45. std::string_view : UNE VUE NON PROPRIÉTAIRE
Code: Select all
std::string_viewIl ne possède pas leur mémoire.
Conceptuellement :
Code: Select all
struct StringViewConcept
{
const char* data;
std::size_t size;
};
Une
Code: Select all
string_viewCode: Select all
pointeur + longueur
Depuis un littéral :
Code: Select all
std::string_view view{
"Kernel"
};
Code: Select all
std::stringCode: Select all
std::string text{
"Kernel"
};
std::string_view view{
text
};
Code: Select all
const char buffer[] = {
'A',
'B',
'C',
'D'
};
std::string_view view{
buffer,
4
};
le dernier buffer ne possède même pas nécessairement de
Code: Select all
'\0'La longueur est explicitement connue.
47. std::string_view NE COPIE PAS LES CARACTÈRES
Considérons :
Code: Select all
std::string text{
"Windows Kernel"
};
std::string_view view{
text
};
Code: Select all
text
|
v
+---+---+---+---+---+---+---+---+---+---+
| W | i | n | d | o | w | s | | K |...|
+---+---+---+---+---+---+---+---+---+---+
^
|
view.data()
Elle pointe vers le stockage existant.
Cela évite :
- une allocation ;
- une copie ;
- une destruction de chaîne temporaire.
Exemple :
Code: Select all
std::string_view text{
"Windows Kernel"
};
std::string_view part =
text.substr(
8,
6
);
Code: Select all
partCode: Select all
"Kernel"
La nouvelle vue référence seulement une sous-zone.
Conceptuellement :
Code: Select all
texte original
W i n d o w s K e r n e l
^
|
part.data()
part.size() == 6
Exemple :
Code: Select all
std::string_view command{
"CMD:START"
};
command.remove_prefix(4);
Code: Select all
"START"
La view a seulement modifié :
- son pointeur de début ;
- sa longueur.
Code: Select all
std::string_view file{
"driver.sys"
};
file.remove_suffix(4);
Code: Select all
"driver"
Imagine une commande :
Code: Select all
SET PRIORITY 12
Code: Select all
std::string input;
Code: Select all
std::string_view command{
input
};
Code: Select all
constexpr
std::string_view prefix{
"SET PRIORITY "
};
if (
command.starts_with(prefix)
)
{
command.remove_prefix(
prefix.size()
);
}
Code: Select all
"12"
Code: Select all
int priority{};
auto result =
std::from_chars(
command.data(),
command.data()
+ command.size(),
priority
);
51. LE GRAND DANGER DE string_view : LA DURÉE DE VIE
Puisque la view ne possède pas la mémoire, elle dépend entièrement de la source.
Exemple incorrect :
Code: Select all
std::string_view getName()
{
std::string name{
"Windows"
};
return name;
}
Code: Select all
name
|
v
[ caractères ]
^
|
view
Code: Select all
nameSon stockage n'est plus valide.
Mais la view retournée contient toujours une adresse.
Elle devient :
Code: Select all
dangling
elle pointe vers une zone dont la durée de vie est terminée.
C'est le même problème qu'un pointeur ou une référence vers une variable locale détruite.
52. string_view ET OBJETS TEMPORAIRES
Autre erreur :
Code: Select all
std::string_view view =
std::string{
"Temporary"
};
Code: Select all
std::stringLa view peut alors continuer à référencer l'ancien stockage.
Règle fondamentale :
Code: Select all
la source doit vivre
au moins aussi longtemps
que la string_view
53. string_view N'EST PAS FORCÉMENT TERMINÉE PAR ZÉRO
Considérons :
Code: Select all
std::string text{
"ABCDEF"
};
std::string_view view{
text.data() + 1,
3
};
Code: Select all
"BCD"
Code: Select all
B C D E F \0
^
fin logique de view
Code: Select all
DCode: Select all
ECode: Select all
\0Faire :
Code: Select all
printf(
"%s",
view.data()
);
Code: Select all
BCDEF
Code: Select all
BCD
Code: Select all
printf("%s")Code: Select all
view.size()Il continue jusqu'au terminateur nul.
C'est une distinction extrêmement importante.
54. API POINTEUR + TAILLE ET string_view
Si l'API accepte explicitement une taille, la situation est différente.
Exemple conceptuel :
Code: Select all
send(
socket,
view.data(),
view.size(),
0
);
Elle n'a pas besoin d'un
Code: Select all
\055. QUAND UTILISER std::string ?
Utilise une chaîne propriétaire lorsque l'objet doit posséder les caractères.
Exemple :
Code: Select all
class Process
{
private:
std::string m_name;
};
Code: Select all
ProcessIl est logique que la classe en possède une copie.
56. QUAND UTILISER std::string_view ?
Pour une fonction qui veut seulement lire temporairement du texte :
Code: Select all
void parse(
std::string_view input
);
La fonction n'a pas besoin :
- d'allouer ;
- de copier ;
- de posséder les caractères.
57. QUAND UTILISER const char* ?
Tu en as encore besoin notamment lorsque :
- une API C le demande ;
- une bibliothèque historique le demande ;
- tu manipules directement un buffer C ;
- tu travailles avec des interfaces ABI.
Code: Select all
const char*
La taille n'est pas intrinsèquement incluse.
Avec une chaîne C, la fin est souvent trouvée grâce à
Code: Select all
\0Code: Select all
std::string
Connaît sa longueur.
Peut allouer et réallouer.
Code: Select all
std::string_view
Conserve :
- une vue vers les caractères ;
- une longueur.
59. FORMATAGE MODERNE DES CHAÎNES
Le C utilise historiquement
Code: Select all
printf()Code: Select all
std::coutCode: Select all
std::formatCode: Select all
std::printCode: Select all
std::printlnCréer une chaîne formatée :
Code: Select all
std::string text =
std::format(
"pid={} size={}",
pid,
size
);
Code: Select all
std::println(
"PID={} SIZE={}",
pid,
size
);
Hexadécimal :
Code: Select all
std::println(
"value=0x{:X}",
value
);
- : hexadécimal minuscule ;
Code: Select all
{:x} - : hexadécimal majuscule ;
Code: Select all
{:X} - : ajoute le préfixe
Code: Select all
{:#x};Code: Select all
0x - : binaire ;
Code: Select all
{:b} - : binaire avec préfixe
Code: Select all
{:#b}.Code: Select all
0b
Code: Select all
std::println(
"EAX=0x{:08X}",
eax
);
Code: Select all
eax == 0x2ACode: Select all
EAX=0x0000002A
Code: Select all
std::uint64_t address{
0x7FF612341000
};
std::println(
"Address=0x{:016X}",
address
);
Le formatage permet aussi de contrôler l'alignement :
Code: Select all
{:<10} // gauche
{:>10} // droite
{:^10} // centre
Code: Select all
std::println(
"{:*^12}",
"CPU"
);
Code: Select all
double value{
3.14159265
};
std::println(
"{:.2f}",
value
);
Code: Select all
3.14
Code: Select all
{:e}Code: Select all
{:g}Les versions modernes de la bibliothèque peuvent également formater certains conteneurs/ranges, ce qui est utile pour les tests et le debug.
Enfin, le système de formatage peut être étendu à tes propres types grâce aux formatters personnalisés. Un type représentant des registres peut, par exemple, être affiché automatiquement sous une forme du genre :
Code: Select all
RIP=0x00007FF612341000
RSP=0x0000008AF23FF000
RAX=0x000000000000002A
Attention : formatage et sérialisation ne sont pas la même chose.
Une sortie lisible destinée à un humain n'est pas automatiquement un format de fichier ou de protocole stable. Une sérialisation doit définir précisément l'encodage, les tailles, l'ordre des champs, les versions et la compatibilité.
72. STRING, STRING_VIEW ET OWNERSHIP
Le concept d'ownership est central en C++.
Demande-toi toujours :
Qui garantit que cette mémoire existe encore ?
Avec :
Code: Select all
std::string text;
Code: Select all
textAvec :
Code: Select all
std::string_view view;
Quelqu'un d'autre doit garantir leur durée de vie.
Avec :
Code: Select all
const char* ptr;
- la taille ;
- la propriété ;
- la durée de vie.
73. ERREUR : RETOURNER UN POINTEUR VERS UNE CHAÎNE LOCALE
Exemple mauvais :
Code: Select all
const char* getName()
{
std::string name{
"Kernel"
};
return name.c_str();
}
Code: Select all
name
Le pointeur retourné référence alors un stockage invalide.
C'est un dangling pointer.
Même problème avec :
Code: Select all
std::string_view
74. BONNE SOLUTION : RETOURNER std::string
Si la fonction doit produire un nouveau texte dont le caller devient propriétaire :
Code: Select all
std::string getName()
{
return "Kernel";
}
Il ne faut pas retourner un pointeur vers une variable locale pour "éviter une copie".
75. PASSER UNE CHAÎNE À UNE FONCTION
Plusieurs possibilités existent.
Copie :
Code: Select all
void f(
std::string text
);
Référence constante :
Code: Select all
void f(
const std::string& text
);
Code: Select all
std::stringVue :
Code: Select all
void f(
std::string_view text
);
Le meilleur choix dépend du contrat.
76. std::string_view COMME PARAMÈTRE DE LECTURE
Exemple :
Code: Select all
bool isDriver(
std::string_view filename
)
{
return
filename.ends_with(
".sys"
);
}
Code: Select all
isDriver(
"ntfs.sys"
);
Code: Select all
std::string name{
"disk.sys"
};
isDriver(name);
Elle le consulte uniquement pendant son exécution.
77. NE PAS STOCKER UNE string_view SANS RÉFLÉCHIR
Exemple :
Code: Select all
class Process
{
private:
std::string_view m_name;
};
Mais seulement si l'objet référencé reste vivant assez longtemps.
Si le constructeur reçoit :
Code: Select all
Process{
std::string{
"notepad.exe"
}
};
La classe conserve alors une view invalide.
Pour les données appartenant réellement à l'objet, un
Code: Select all
std::string78. INTEROPÉRABILITÉ WIN32
Le monde Windows utilise très souvent les chaînes wide.
Exemple :
Code: Select all
std::wstring path{
L"C:\\Windows\\System32"
};
Code: Select all
CreateFileW(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
Code: Select all
std::stringIl existe :
- ;
Code: Select all
std::string - ;
Code: Select all
std::wstring - d'autres spécialisations.
Code: Select all
std::wstring
LPCWSTR
LPWSTR
WCHAR
Sous POSIX, beaucoup d'interfaces utilisent directement :
Code: Select all
const char*
Code: Select all
std::string path{
"/tmp/data.bin"
};
int fd =
open(
path.c_str(),
O_RDONLY
);
Le fait de savoir passer de :
Code: Select all
std::string
Code: Select all
const char*
80. CHAÎNES ET SOCKETS
Les sockets illustrent parfaitement le modèle buffer + taille.
Exemple :
Code: Select all
std::string message{
"HELLO"
};
send(
sock,
message.data(),
message.size(),
0
);
Il envoie des octets.
L'API reçoit :
- l'adresse des octets ;
- le nombre d'octets.
Même si
Code: Select all
std::stringPour du binaire brut, on préfère souvent des structures comme :
Code: Select all
std::vector<std::byte>
Code: Select all
std::array<std::byte, N>
Code: Select all
std::vector<std::byte>
buffer(4096);
Code: Select all
read(
fd,
buffer.data(),
buffer.size()
);
ce sont des octets, pas du texte.
82. '\0' AU MILIEU D'UN std::string
Un
Code: Select all
std::stringExemple :
Code: Select all
std::string data{
"ABC\0DEF",
7
};
Code: Select all
7
Code: Select all
strlen(
data.c_str()
)
Code: Select all
\0Elle verra seulement :
Code: Select all
ABC
Code: Select all
longueur std::string
Code: Select all
longueur chaîne C
83. TEXTES, ENCODAGE ET NOMBRE DE CARACTÈRES
Il faut également faire attention à un point plus avancé.
Un
Code: Select all
charAvec UTF-8, un caractère visible peut être représenté par plusieurs octets.
Donc :
Code: Select all
string.size()
Pour du développement système, il est surtout important de ne pas confondre :
- octets ;
- unités d'encodage ;
- caractères logiques ;
- glyphes.
Imaginons :
Code: Select all
void parse(
std::string input
);
Si la fonction ne fait que lire :
Code: Select all
void parse(
std::string_view input
);
Autre exemple :
Code: Select all
std::string token =
text.substr(
begin,
length
);
Une vue :
Code: Select all
std::string_view token =
view.substr(
begin,
length
);
Cela compte dans les parsers de gros fichiers ou protocoles.
85. PERFORMANCE : reserve() LORS DE CONCATÉNATIONS
Imagine :
Code: Select all
std::string output;
for (...)
{
output += piece;
}
Si une estimation est connue :
Code: Select all
std::string output;
output.reserve(
estimatedSize
);
86. PERFORMANCE : NE PAS OPTIMISER À L'AVEUGLE
Le fait qu'une abstraction puisse allouer ne signifie pas qu'il faut revenir systématiquement à :
Code: Select all
malloc()
char*
memcpy()
Le but est de comprendre leur coût et de choisir correctement.
87. EXEMPLE COMPLET : PARSER UNE COMMANDE SYSTÈME
Supposons que ton programme reçoive :
Code: Select all
READ 4096
Code: Select all
std::string input{
"READ 4096"
};
Code: Select all
std::string_view view{
input
};
Code: Select all
constexpr
std::string_view prefix{
"READ "
};
Code: Select all
if (
!view.starts_with(prefix)
)
{
std::println(
"Commande inconnue"
);
return;
}
Code: Select all
view.remove_prefix(
prefix.size()
);
Code: Select all
"4096"
Code: Select all
std::size_t requestedSize{};
auto result =
std::from_chars(
view.data(),
view.data()
+ view.size(),
requestedSize
);
Code: Select all
if (
result.ec
!=
std::errc{}
)
{
std::println(
"Taille invalide"
);
return;
}
Code: Select all
std::println(
"Lecture demandée : {} octets",
requestedSize
);
- comme propriétaire ;
Code: Select all
std::string - comme vue ;
Code: Select all
string_view - ;
Code: Select all
starts_with() - ;
Code: Select all
remove_prefix() - ;
Code: Select all
from_chars() - formatage moderne.
aucune sous-chaîne temporaire n'est nécessaire pour parser le nombre.
88. EXEMPLE COMPLET : AFFICHAGE D'INFORMATIONS PROCESSUS
Supposons :
Code: Select all
struct ProcessInfo
{
unsigned long pid;
std::uint64_t baseAddress;
std::uint32_t flags;
};
Code: Select all
ProcessInfo process{
1337,
0x7FF612341000,
0x25
};
Code: Select all
std::println(
"PID={} BASE=0x{:016X} FLAGS=0x{:08X}",
process.pid,
process.baseAddress,
process.flags
);
Code: Select all
PID=1337 BASE=0x00007FF612341000 FLAGS=0x00000025
89. EXEMPLE : std::string ET API C
Supposons une fonction externe :
Code: Select all
void legacy_log(
const char* message
);
Code: Select all
std::string message{
"Driver loaded"
};
legacy_log(
message.c_str()
);
Code: Select all
std::stringL'API C reçoit simplement une vue C temporaire vers le contenu.
Il faut évidemment que l'API ne conserve pas le pointeur après la destruction ou la modification de la chaîne, sauf contrat spécifique.
90. EXEMPLE : POURQUOI GARDER data() PEUT ÊTRE DANGEREUX
Code: Select all
std::string text{
"ABC"
};
const char* ptr =
text.data();
text +=
"DEF";
std::println(
"{}",
ptr
);
Pourquoi ?
Parce que
Code: Select all
text += "DEF"Le pointeur mémorisé avant l'opération peut ne plus être valide.
Règle générale :
Après une opération pouvant réallouer, récupère à nouveau le pointeur si nécessaire.
91. EXEMPLE : string_view ET MODIFICATION DE LA SOURCE
Code: Select all
std::string text{
"Hello"
};
std::string_view view{
text
};
text +=
" World";
Code: Select all
textLa
Code: Select all
viewUne string_view n'est pas magiquement reliée à l'objet source.
Elle conserve simplement une adresse et une longueur.
Si le stockage source bouge, la view ne se met pas automatiquement à jour.
92. LES CHAÎNES ET LE DÉVELOPPEMENT KERNEL
En kernel, les règles changent souvent.
La bibliothèque standard C++ peut être absente, limitée ou déconseillée selon l'environnement.
Sous Windows kernel, on rencontre notamment des structures spécifiques comme :
Code: Select all
UNICODE_STRING
Code: Select all
buffer
length
maximum length
Code: Select all
adresse + taille
Code: Select all
string_view93. LES CHAÎNES ET LES API WINDOWS
Une structure conceptuelle Windows peut comporter :
Code: Select all
PWSTR Buffer;
USHORT Length;
USHORT MaximumLength;
Code: Select all
pointer
length
capacity
Les abstractions C++ ne sont pas séparées du bas niveau.
Elles formalisent souvent les mêmes idées avec des règles plus sûres.
94. LE BON MODÈLE MENTAL POUR UN DÉVELOPPEUR SYSTÈME
Tu peux retenir :
Code: Select all
const char*
Souvent associé à une chaîne C terminée par zéro, mais le pointeur lui-même ne stocke ni longueur ni ownership.
Code: Select all
std::string
Il gère :
- la taille ;
- la capacité ;
- le stockage ;
- la destruction.
Code: Select all
std::string_view
Elle représente conceptuellement :
Code: Select all
adresse + longueur
- Confondre et longueur de chaîne.
Code: Select all
sizeof(pointer) - Oublier le d'une chaîne C.
Code: Select all
\0 - Utiliser sans garantir la capacité.
Code: Select all
strcpy() - Comparer des avec
Code: Select all
char*en pensant comparer le texte.Code: Select all
== - Conserver après une réallocation.
Code: Select all
string.data() - Retourner d'une chaîne locale.
Code: Select all
c_str() - Créer une vers un temporaire.
Code: Select all
string_view - Supposer qu'une est terminée par zéro.
Code: Select all
string_view - Utiliser avec une vue non terminée.
Code: Select all
printf("%s") - Créer des copies de inutiles dans un parser.
Code: Select all
std::string
Pour ton niveau professionnel futur, je te conseille de connaître sans hésitation :
Code: Select all
strlen()
strcmp()
std::string
std::string::size()
std::string::data()
std::string::c_str()
std::string::find()
std::string::substr()
std::string::reserve()
std::string::capacity()
std::string_view
std::string_view::data()
std::string_view::size()
std::string_view::substr()
std::string_view::remove_prefix()
std::string_view::remove_suffix()
std::from_chars()
std::to_chars()
97. CE QUI PEUT ÊTRE CONNU SANS ÊTRE MÉMORISÉ IMMÉDIATEMENT
Tu n'as pas besoin de mémoriser chaque détail de :
- tous les spécificateurs de format ;
- tous les formats de ranges ;
- les custom formatters complexes ;
- chaque conversion numérique disponible.
98. MINI-VÉRIFICATIONS À SAVOIR FAIRE
Tu dois être capable d'expliquer immédiatement les cas suivants :
Code: Select all
char text[] = "ABC";
sizeof(text); // 4
strlen(text); // 3
Code: Select all
Avec :
Code: Select all
const char* text = "ABC";
sizeof(text);
Code: Select all
sizeofEnfin, une
Code: Select all
std::string_viewCode: Select all
std::string103. EXERCICE PRATIQUE BAS NIVEAU
Écris un programme qui reçoit une chaîne :
Code: Select all
"WRITE 1024"
- stocke la chaîne dans un ;
Code: Select all
std::string - crée une ;
Code: Select all
std::string_view - vérifie le préfixe ;
Code: Select all
"WRITE " - retire le préfixe sans copie ;
- convertit avec
Code: Select all
"1024";Code: Select all
std::from_chars - affiche la valeur en décimal et en hexadécimal.
Code: Select all
SIZE=1024
HEX=0x400
104. SYNTHÈSE GÉNÉRALE
Les chaînes sont un excellent exemple de la philosophie du C++ moderne.
Le langage ne t'oblige pas à abandonner le bas niveau.
Il te permet plutôt de choisir plusieurs niveaux d'abstraction.
Au niveau le plus brut :
Code: Select all
const char*
Avec :
Code: Select all
std::string
Avec :
Code: Select all
std::string_view
Code: Select all
adresse + longueur
Un programme professionnel peut très bien faire :
Code: Select all
std::string command;
puis :
Code: Select all
std::string_view view{
command
};
puis :
Code: Select all
std::from_chars(
view.data(),
view.data() + view.size(),
value
);
et finalement :
Code: Select all
std::println(
"VALUE=0x{:08X}",
value
);
C'est exactement le type de combinaison qu'il faut rechercher :
abstractions modernes lorsque leur coût et leur contrat sont adaptés, accès mémoire direct lorsque c'est nécessaire.
À RETENIR ABSOLUMENT
- Une chaîne C est généralement terminée par .
Code: Select all
\0 - parcourt la chaîne jusqu'au terminateur.
Code: Select all
strlen() - ne donne pas la longueur du texte.
Code: Select all
sizeof(pointer) - Un buffer possède une capacité ; le texte possède une longueur.
- Les fonctions C historiques peuvent provoquer des dépassements si les tailles sont mal gérées.
- possède et gère sa mémoire.
Code: Select all
std::string - permet d'accéder au stockage contigu.
Code: Select all
std::string::data() - permet l'interopérabilité avec de nombreuses API C.
Code: Select all
c_str() - Une réallocation peut invalider les pointeurs vers le stockage interne.
- ne possède pas les données.
Code: Select all
std::string_view - Une représente essentiellement une adresse et une longueur.
Code: Select all
string_view - Une view peut devenir dangling si sa source disparaît ou réalloue.
- Une n'est pas forcément terminée par zéro.
Code: Select all
string_view - et
Code: Select all
from_chars()sont particulièrement intéressants pour le parsing bas niveau.Code: Select all
to_chars() - Le formatage hexadécimal et binaire est très utile pour les outils système.
- En développement bas niveau, l'objectif n'est pas d'éviter toutes les abstractions, mais de comprendre précisément ce qu'elles font en mémoire.
