Skip to content

Classe inversée : chaines de caractètes

1. Cours

Pour maîtriser les chaînes en C, il faut comprendre comment l'ordinateur manipule le texte. Contrairement à Python ou Java, le C ne cache rien : vous manipulez directement la mémoire et des nombres.

Le grand secret : char est un nombre

Le type char est un "faux ami". Il ne stocke pas une lettre, mais un petit entier (généralement sur 1 octet, soit 8 bits).

L'ordinateur ne comprend que les nombres. Pour afficher du texte, il utilise une table de correspondance appelée Table ASCII.

  • Le nombre 65 correspond à 'A'.
  • Le nombre 97 correspond à 'a'.
  • Le nombre 48 correspond à '0'.

Conséquence importante : On peut faire des maths avec des lettres !

char lettre = 'A';  // En mémoire, c'est la valeur 65
lettre = lettre + 1; 
printf("%c", lettre); // Affiche 'B' (car 65 + 1 = 66, et 66 est le code de 'B')

Tip

Vous n'avez donc pas à connaître les valeurs de la table ASCII. Il faut simplement savoir :

  • que les lettres minuscules se suivent ('b'='a'+1, 'c'='a'+2, ... 'z'='a'+25)
  • que les lettres majuscules se suivent ('B'='A'+1, 'C'='A'+2, ... 'Z'='A'+25)
  • que les chiffres se suivent ('1'='0'+1, '2'='0'+2, ... '9'='0'+9)

Et donc 'a' - 'A' donne la distance constante entre une minuscule et sa majuscule (32 pour info, mais pas besoin de le retenir !).

Le tableau et le \0

Une chaîne de caractères est une suite de ces nombres (char) stockés les uns à la suite des autres en mémoire. Il n'y a donc pas de type string, on utilise simplement un tableau de char donc un char*. Inutile en revanche de passer la taille aux différentes fonctions comme pour un simple tableau "normal" de char. Qu'est-ce qui les distingue ? Comment l'ordinateur sait-il où s'arrête la phrase ? Il n'y a pas de variable "taille" cachée. La convention universelle en C est le Caractère Nul ('\0').

  • C'est un octet dont la valeur numérique est 0.
  • Il sert de "point final". Sans lui, les fonctions continuent de lire la mémoire indéfiniment jusqu'au crash.

Exemple : "Salut" est un tableau de 6 char (et pas 5) : ['S'] ['a'] ['l'] ['u'] ['t'] ['\0']

Le piège de la mémoire : Zone Statique vs Pile

Il y a deux façons de déclarer une chaîne :

Cas A : La chaîne littérale (Zone Lecture Seule / Text Segment)

char *str = "Bonjour";
  • Mécanisme : La chaîne "Bonjour" est gravée dans le marbre dans une zone protégée de l'exécutable (segment text ou rodata, dans la fameuse zone statique). Le pointeur str pointe vers elle.
  • Danger : C'est de la lecture seule.
  • Si vous faites : str[0] = 'b'; -> ca compile, mais si vous tentez une exécution CRASH immédiat (l'OS interdit l'écriture dans cette zone).

Cas B : Le tableau sur la Pile (Stack)

char str[] = "Bonjour";
ou équivalent
char str[8] = "Bonjour";
char str[] = {'B','o','n','j','o','u','r','\0'};

  • Mécanisme : Le compilateur réserve de la place sur la pile (stack) et recopie les caractères "Bonjour" dedans.
  • Avantage : C'est votre copie locale.
  • Si vous faites : str[0] = 'b'; -> Succès. La chaîne devient "bonjour".

2. Exercices Pratiques

Consigne générale : N'utilisez pas les fonctions de <string.h> (strlen, strcpy, etc.) sauf mention contraire. Le but est de réinventer la roue pour comprendre comment elle tourne.

Exercice 1 : Arithmétique des caractères

Puisque char est un entier, on peut le manipuler. Écrivez une fonction void rot1(char *str) qui décale chaque lettre de la chaîne de +1 dans la table ASCII (sauf le caractère nul bien sûr).

  • "ABC" doit devenir "BCD".
  • "Hal" doit devenir "Ibm".

Exercice 2 : L'investigation Mémoire

Copiez, compilez et analysez ce code pour visualiser la théorie de la Partie 1.

include <stdio.h>

int main() {
    char *chaine_statique = "Test";
    char chaine_pile[] = "Test";

    printf("Adresse statique : %p\n", chaine_statique);
    printf("Adresse pile     : %p\n", chaine_pile);

    // Test A :
    printf("Valeur du premier char (statique) : %d (ASCII) = %c\n", *chaine_statique, *chaine_statique);

    // Test B : Tentative de modification (Crash ?)
    // chaine_statique[0] = 'Z'; // Décommentez pour tester le crash

    // Test C : Modification valide
    chaine_pile[0] = 'Z';
    printf("Nouvelle chaine pile : %s\n", chaine_pile);

    return 0;
}
  1. Les adresses mémoire sont-elles proches ? Pourquoi ?
  2. Pourquoi le Test B fait-il planter le programme alors que la syntaxe est correcte ?

Exercice 3 : my_strlen (Taille)

Écrivez une fonction int my_strlen(char* str) qui renvoie le nombre de caractères avant le \0.

  • Utilisez une boucle while.
  • N'oubliez pas : str[i] est la valeur du caractère. Si str[i] == \0, c'est la fin.

Exercice 4 : my_strcpy (Copie)

On ne peut pas copier un tableau avec =. Il faut copier case par case. Écrivez char* my_strcpy(char* dest, char* src, int max).

  • Copiez au plus max char de src vers dest.
  • N'oubliez pas de mettre quoi qu'il arrive un \0 à la fin de dest !
  • Quelle est la taille minimale d'allocation du tableau dest pour que ca fonctionne correctement ?

Exercice 5 : my_strcmp (Comparaison)

Écrivez int my_strcmp(char *s1, char *s2).

  • Parcourez les deux chaînes.
  • Tant que s1[i] est égal à s2[i] et qu'on n'est pas à la fin d'une des chaînes, continuez.
  • Retournez la soustraction s1[i] - s2[i].
    • Si le résultat est < 0, alors s1 est "avant" s2 dans l'alphabet (ex: 'A' (65) - 'B' (66) = -1).
    • Si le résultat est 0, les chaînes sont identiques.

Exercice 6 : Le Majusculeur

En utilisant le fait que dans la table ASCII, les minuscules et les majuscules sont décalées d'une valeur constante (calculable), écrivez une fonction void to_upper(char *str) qui transforme toutes les minuscules d'une chaîne en majuscules, sans toucher aux autres caractères (ponctuation, chiffres, déjà majuscules).


Sujet rédigé à l'aide de ChatGPT et Gemini