Chaînes de caractères en C
Se termine par \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[3] s = "abc";?Il manque une case pour '\0'. -
Que pensez-vous de
char[10] s = "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. -
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;
}
- Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char* s = "Bonjour";
return s;
}
- 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;
}
Longueur d'une chaîne de caractères
size_t strlen(const char *s);
- Ecrire soi-même une fonction
int len(char* s)qui renvoie la longueur de la chaînes.
Parcours d'une chaîne de caractères
- Ecrire une fonction qui teste d'appartenance d'un caractère à une chaîne.
- Ecrire une fonction qui transforme une chaîne de caractères en sa version en minuscule.
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, puis\0.
Comparaison de chaînes
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);

Cette fonction 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. Attention, si ce n'est pas alloué suffisamment :
.
char * strcpy(char * restrict dest, const char * restrict source);

strncpy fait la même chose mais dans la limite de len caractères.
size_t strncpy(char * restrict dst, const char restrict * src, size_t len);
size_t strlcpy(char * restrict dst, const char * restrict src, size_t dsze);
memcpy etc.
void * memcpy ( void * destination, const void * 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");
Variante efficace
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.
- Ecrire une version
mystrcatqui fait la même chose questrcatmais 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
- En utilisant
memcpy, écrire une variantestring_concatqui renvoie une nouvelle chaîne de caractères qui est la concaténation des1ets2.
La signature de memcpy est
void * memcpy( void * restrict dst, const void * restrict src, size_t nbBytesToCopy );
/*
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
- Ecrire une fonction qui prend une chaîne
s1et qui lui concatènes2. Si pas assez de mémoire, on réalloues1. La fonction renvoie la nouvelle chaîne. Dans tous les cas, la chaînes1initiale 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
- Dessiner un schéma mémoire de
#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
int main(int argc, char *argv[]) {
...
}
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
Exercices
- Modifier les projets précédents pour qu'ils prennent en entrée les arguments en ligne de commande
- Ecrire un programme qui prend en entrée un texte et qui renvoie sa version justifiée.
Aller plus loin
- article philosophique sur les chaînes qui finissent par \0 : https://queue.acm.org/detail.cfm?id=2010365