Chaînes de caractères en C
Se termine par \0
En C, une chaîne de caractères est une suite de char (octets) qui termine par un caractère/octet spécial : le caractère 0 de code 0.
"chouette"
Chaîne de caractères = pointeur sur char
On peut initialiser
char* s = "chouette";
En mémoire, s est un pointeur, une adresse mémoire, vers une zone de la mémoire avec 9 octets alloués où on a :
QCM
-
Que pensez-vous de
char s[3] = "abc";?Il manque une case pour ‘\0’. -
Que pensez-vous de
char s[10] = "abc";?Pas de soucis, le compilateur complète avec des 0. -
Que pensez-vous de
char s[] = "abc";?Pas de soucis, le compilateur comprend qu’il faut allouer 4 octets, et d’ailleurs il n’alloue que 4 octets. -
Comment accéder à la i-ème lettre ?
a[i]. -
Que pensez-vous du programme suivant ?
char s[] = "abca";
s[2] = 'b';
- Que pensez-vous du programme suivant ?
char* s = "abca";
s[2] = 'b';
- Est-ce que ce programme est correct bien que le tableau ne finissent pas par
0?
char A[3];
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
<string.h> qui gèrent les chaînes de caractères ! En effet, A[3] (qui sort de la zone allouée) n’est peut-être pas 0 et donc les fonctions vont continuer à lire des parties de la mémoire indéterminée jusqu’à trouver un 0.
- Est-ce que ce programme est correct ?
char* A = malloc(2);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[2] n’est pas alloué.
- Est-ce que ce programme est correct ?
char* A = malloc(3);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
char* A = malloc(4);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;
0.
char* A = malloc(10);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;
0. Il y a des cases non utilisées mais ce n’est pas grave.
Le C c’est vraiment du bas niveau !
Considérons deux chaînes de caractères
char s[10], t[10];
-
Peut-on écrire
s = "abc";?Non, car les tableaux ne sont pas assignables. -
Peut-on écrire
t = s;?Pareil, non. On ne peut pas faire d’affectation de tableaux (c’est parce que ce sont des tableaux, on peut par contre faire des assignations de pointeurs, et donc de char *). -
Peut-on initialiser
char u[10] = "abc";?Oui, à l’initialisation c’est bon. C’est équivalent à char u[10] = {'a', 'b', 'c', '\0'}; -
Peut-on tester
s == t?Oui, on peut toujours tester l’égalité d’adresse mémoire de tableaux ou de pointeurs :). -
Que teste
s == t?Que l’adresse mémoire sest égale à l’adresse mémoiret. -
Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char s[50] = "Bonjour";
return s;
}
"Bonjour" puis on renvoie l’adresse (s) alors que la pile va être écrasée !
- Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char* s = "Bonjour";
return s;
}
"Bonjour". On ne peut pas modifier la chaîne de caractères. Il ne faut pas appeler free sinon erreur (on ne peut pas libérer une zone de données en lecture seule !).
- Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char* s = malloc(10*sizeof *s);
s[0] = 'h';
s[1] = 'i';
s[2] = '!';
s[3] = '\0';
return s;
}
"hi!" (attention cependant à la constante volante 10). On peut modifier la chaîne de caractères. Il ne faut pas oublier de faire free quelque part.
Longueur d’une chaîne de caractères
La bibliothèque string.h propose la fonction suivante :
size_t strlen(const char *s);
Elle prend une chaîne de caractères et renvoie sa longueur, i.e. le nombre d’octets de caractères. Attention, il faut que la chaîne finisse par 0, sinon gare à vous ! Et ce caractère 0 n’est pas comptabilisé.
int len(const char* s) qui renvoie la longueur de la chaîne s.
size_t len(const char *str) {
const char *ptr = str;
while (*ptr != '\0') ptr++;
return ptr - str;
}
La fonction utilise l’arithmétique des pointeurs.
Parcours d’une chaîne de caractères
Considérons :
char* s = "Bonjour tout le monde";
-
Que vaut
strlen(s)?21 car il y a 21 caractères dans "Bonjour tout le monde". -
Que vaut
sizeof(s)?8 car il faut 8 octets pour stocker une adresse mémoire.
Considérons :
char[] s = "Bonjour tout le monde";
-
Que vaut
strlen(s)?21 car il y a 21 caractères dans "Bonjour tout le monde". -
Que vaut
sizeof(s)?22 car il faut 22 octets pour mettre la chaîne de caractères "Bonjour tout le monde"de 21 caractères, puis0.
Comparaison de chaînes
La bibliothèque string.h propose une fonction de comparaison de deux chaînes de caractères :
int strcmp(const char *s1, const char *s2)
strcmp("abricot", "chat") | strcmp("chat", "chat") | strcmp("chat", "abricot") |
|---|---|---|
< 0 | 0 | > 0 |
Copie de chaînes
strdup
char * strdup( const char * source);
La fonction strup duplique une chaîne de caractère. Elle renvoie une copie de la chaîne de caractères source :
- Elle alloue une nouvelle zone mémoire avec un
malloc(caché dans l’appelstrdup) de la même taille quesource - En cas de succès, elle copie
sourcevers cette nouvelle zone mémoire et renvoie un pointeur vers cette zone - En cas d’échec (hé oui, le
mallocpeut échouer), elle renvoie un pointeur nul.
⚠ Attention à libérer la mémoire avec free de la copie créée.
strcpy
strcpy copie src dans dst que l’on a déjà alloué préalablement.
char * strcpy(char * restrict dest, const char * restrict source);
Attention, la fonction strcpy est dangereuse. Si dst n’est pas alloué suffisamment, on écrit en dehors de la zone allouée et c’est le mal : .
char* src = strup("mon buffer qui est aussi un peu la source");
char* dst = src + 5;
strcpy(dst, src); // on ne respecte pas le contrat car aliasing
strncpy
Pour ne pas dépasser la zone allouée, une première alternative, dangereuse aussi, est strncpy. Elle fait la même chose mais dans la limite de len caractères. Mais attention, si len est trop petit, strncpy n’écrit pas le caractère 0 à la fin des données tronquées.
size_t strncpy(char * restrict dst, const char restrict * src, size_t len);
Fonctions sécurisées de l’annexe K du C11
L’annexe K du C11 a ajouté des fonctions sécurisées comme :
errno_t strcpy_s( char * restrict destination, rsize_t destinationSize, const char * restrict source );
Si jamais destinationSize est trop petit pour accueillir la chaîne de caractères source alors la fonction renvoie un code d’erreur.
Et voici la fonction qui tronque si jamais ça dépasse :
errno_t strncpy_s( char* restrict dest, rsize_t destsz,
const char* restrict src, rsize_t count );
size_t strlcpy(char * restrict dst, const char * restrict src, size_t dsze);
memcpy etc.
void * memcpy ( void * restrict destination, const void * restrict source, size_t nbOctetsACopier );
La même chose mais pour de la mémoire et pas de vérification de caractère nul ou autre. C’est fait pour copier n’importe quoi.
Concaténer deux chaînes
strcat de <string.h>
char * strcat(char* restrict dst, const char * restrict src);
Précondition :
- il faut que
dstsoit suffisamment alloué pour contenir le contenu de la concaténation. Sinon .
Effet :
- place
srcà la fin dedst
char[1000] s = "";
strcat(s, "Bienvenue ");
strcat(s, "à ");
strcat(s, "l'ENS de Lyon");
strcat renvoie :
dst! C’est pour pouvoir faire des cascades d’appel
On peut donc chaîner les appels à strcat comme cela :
char[1000] s = "";
strcat(strcat(strcat(s, "Bienvenue "), "à "), "l'ENS de Lyon");
Malheureusement, la complexité de strcat est en O(|s1| + |s2|).
cf https://www.joelonsoftware.com/2001/12/11/back-to-basics/
On souffre du problème de Shlemiel le peintre.
Exercice
mystrcat qui fait la même chose que strcat mais renvoie un pointeur sur la fin de la chaîne.
char* mystrcat( char* dst, char* src )
{
while (*dst) dst++;
while (*dst++ = *src++);
return --dst;
}
Variante à la Python
memcpy, écrire une variante string_concat qui renvoie une nouvelle chaîne de caractères qui est la concaténation de s1 et s2.
/*
return a new string that is the concatenation of s1 and s2
(in Python, s1 + s2)
**/
char * string_concat(char* s1, char* s2) {
int l1 = strlen(s1);
int l2 = strlen(s2);
char* result = malloc(l1 + l2 + 1);
if(result == NULL)
return NULL;
memcpy(result, s1, l1);
memcpy(result + l1, s2, l2+1);
return result;
}
Variante avec réallocation
s1 et qui lui concatène s2. Si pas assez de mémoire, on réalloue s1. La fonction renvoie la nouvelle chaîne. Dans tous les cas, la chaîne s1 initiale est perdue.
char* strcatrealloc(char* s1, char* s2) {
int l1 = strlen(s1);
int l2 = strlen(s2);
char* result = realloc(s1, l1 + l2 + 1 * sizeof(*result));
if(result == NULL)
return NULL;
memcpy(result + l1, s2, l2 + 1);
return result;
}
Tableaux de chaînes de caractères
#define NB_MAXCHAR 12
char planets[][NB_MAXCHAR] = {"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune", "Pluto"};
- Même chose pour
char *planets[] = {"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune", "Pluto"};
- Même chose pour
#define NB_PLANETS 9
char ** planets = malloc(NB_PLANETS*sizeof(*planets));
planets[0] = "Mercury";
planets[1] = "Venus";
planets[2] = "Earth";
planets[3] = "Mars";
planets[4] = "Jupiter";
planets[5] = "Saturn";
planets[6] = "Uranus";
planets[7] = "Neptune";
planets[8] = "Pluto";
Arguments en ligne de commande
En fait, un programme peut prendre des arguments dans la ligne de commande. Par exemple :
./programme -l arf.txt
Récupération des arguments du programme
On peut les récupérer dans la fonction main du programme qui a deux paramètres argc et argv. La fonction main est appelée avec comme argument argc qui vaut le nombre de paramètres + le programme lui-même. Puis argv est un tableau de chaîne de caractères, chaque chaîne est un argument.
int main(int argc, char *argv[]) {
...
}
Exemple 1
Par exemple si notre programme est programme et l’utilisateur lance la commande
./programme -l arf.txt
alors
argcvaut3 argv[0]contient“programme” argv[1]contient“-l” argv[2]contient“arf.txt” argv[3]est le pointeur .NULL
Exemple 2
Autre exemple, si l’utilisateur lance la commande
./programme -l "miaou dit le chat"
alors
argcvaut3 argv[0]contient“programme” argv[1]contient“-l” argv[2]contient“miaou dit le chat” argv[3]est le pointeur .NULL