Programmation C/Chaînes de caractères
Le langage C offre quelques facilités d'écritures pour simuler les chaînes de caractères à l'aide de tableaux. En plus de cela, certaines fonctions de la bibliothèque standard (et les autres) permettent de faciliter leur gestion. À la différence d'un tableau, les chaînes de caractères respectent une convention : se terminer par le caractère nul, noté codice_1 ("antislash-zero"). Ainsi, pour construire une chaîne de caractères « à la main », il ne faut pas oublier ce caractère final.
On peut noter que, par rapport à ce qui est disponible dans d'autres langages, les fonctions de la bibliothèque standard sont peu pratiques à utiliser. On peut avancer sans trop de risque qu'une des plus grandes lacunes du C provient de sa gestion précaire et limitée des chaînes de caractères, pourtant massivement employées dans d'innombrables logiciels. De plus, une mauvaise utilisation de ces fonctions peut facilement conduire à des bugs. Pour faire court, on ne saurait trop conseiller de soit reprogrammer les fonctions ci-dessous, d'utiliser une bibliothèque externe ou de faire preuve de paranoïa avant leur utilisation.
On notera en particulier que la bibliothèque du langage C, assez ancienne n'apporte pas de fonction particulière pour traiter les spécificités de nouveaux standards tels qu'Unicode. Elle est toutefois assez générique pour effectuer certains opérations basiques. Pour une utilisation plus avancée, une bibliothèque Unicode peut avoir un intérêt.
Les fonctions permettant de manipuler les chaînes de caractères se trouvent dans l'entête codice_2', ainsi pour les utiliser il faut ajouter la commande préprocesseur :
Comparaison de chaînes.
int strcmp(const char * chaine1, const char * chaine2);
int strncmp(const char * chaine1, const char * chaine2, size_t longueur);
Compare les chaînes "chaine1" et "chaine2" et renvoie un entier :
première remarque : lorsque les deux chaînes sont égales, codice_3 renvoie 0, qui a la valeur de vérité "faux". Pour tester l'égalité entre deux chaînes, il faut donc écrire soit codice_4, soit codice_5 mais surtout pas codice_6 qui teste si deux chaînes sont différentes !
seconde remarque : l'opérateur codice_7, dans le cas de pointeurs, teste si les adresses sont égales. Noter codice_8, si "chaine1" et "chaine2" sont des codice_9 revient à tester si les deux chaînes pointent sur la même zone mémoire et non pas à tester l'égalité de leur contenu.
troisième remarque : la comparaison effectuée est une comparaison binaire. Deux chaînes canoniquement équivalentes au sens Unicode sont donc ici considérées différentes.
quatrième remarque : la comparaison n'est pas basée sur un alphabet mais sur un codage de caractère. Ceci conduit à un tri particulier des caractères accentuées, majuscules et minuscules,qui n'est pas nécessairement celui souhaité.
À noter l'existence de deux fonctions de comparaisons de chaîne, insensibles à la casse des caractères et s'adaptant à la localisation en cours, fonctionnant sur le même principe que codice_10 et codice_11, mais dont l'origine provient des systèmes BSD :
int strcasecmp(const char * chaine1, const char * chaine2);
int strncasecmp(const char * chaine1, const char * chaine2, size_t longueur);
Ces fonctions se basent sur les locales .
Longueur d'une chaîne.
size_t strlen(const char * chaine);
Renvoie la longueur de la "chaine" en octets, sans compter le marqueur de fin de chaîne codice_1.
exemple : codice_13 renvoie 8.
Nota: la longueur mesurée en caractères peut être différente de la longueur mesurée en octets suivant le système de codage de caractère utilisé. .
Concaténation et copie de chaînes.
char * strcpy (char *destination, const char *source);
char * strncpy (char *destination, const char *source, size_t n);
char * strcat (char *destination, const char *source);
char * strncat (char *destination, const char *source, size_t n);
codice_14 copie le contenu de "source" à l'adresse mémoire pointé par "destination", incluant le caractère nul final. codice_15 concatène la chaîne "source" à la fin de la chaîne "destination", en y rajoutant aussi le caractère nul final. Toutes ces fonctions renvoient le pointeur sur la chaîne "destination".
Pour "strncpy", on notera que la copie est limité à un nombre d'octets:
Il faut être très prudent lors des copies ou des concaténations de chaînes, car les problèmes pouvant survenir peuvent être très pénibles à diagnostiquer. L'erreur « classique » est de faire une copie dans une zone mémoire non réservée ou trop petite, comme dans l'exemple suivant, "a priori" anodin:
char *copie_chaine(const char *source)
 char *chaine = malloc(strlen(source));
 if (chaine != NULL)
 strcpy(chaine, source);
 return chaine;
Ce code "a priori" correct provoque pourtant l'écriture d'un caractère dans une zone non allouée. Les conséquences de ce genre d'action sont totalement imprévisibles, pouvant au mieux passer inaperçue, ou au pire écraser des structures de données critiques dans la gestion des blocs mémoires et engendrer des accès mémoire illégaux lors des prochaines tentatives d'allocation ou de libération de bloc, i.e. le cauchemar de tout programmeur. Dans cet exemple il aurait bien évidemment fallu écrire :
 char * chaine = malloc( strlen(source) + 1 );
À noter aussi, tout aussi grave, que strlen() ne doit pas être appelée avec comme argument un pointeur 'source' valant NULL ou pointant sur autre chose qu'un espace mémoire dûment occupé par une chaîne C terminée par un caractère 'NUL' .
Une autre erreur, beaucoup plus fréquente hélas, est de copier une chaîne dans un tableau de caractères local, sans se soucier de savoir si ce tableau est capable d'accueillir la nouvelle chaîne. Les conséquences peuvent ici être beaucoup plus graves qu'un simple accès illégal à une zone mémoire globale.
Un écrasement mémoire ("buffer overflow") est considéré comme un défaut de sécurité relativement grave, puisque, sur un grand nombre d'architectures, un « attaquant » bien renseigné sur la structure du programme peut effectivement lui faire exécuter du code arbitraire. Ce problème vient de la manière dont les variables locales aux fonctions et certaines données internes sont stockées en mémoire. Comme le C n'interdit pas d'accéder à un tableau en dehors de ses limites, on pourrait donc, suivant la qualité de l'implémentation, accéder aux valeurs stockées au-delà des déclarations de variables locales. En fait, sur un grand nombre d'architectures, les variables locales sont placées dans un espace mémoire appelé "pile", avec d'autres informations internes au système, comme "l'adresse de retour de la fonction", c'est-à-dire l'adresse de la prochaine instruction à exécuter après la fin de la fonction. En s'y prenant bien, on peut donc écraser cette valeur pour la remplacer par l'adresse d'un autre bout de code, qui donnerait l'ordre d'effacer le disque dur, par exemple ! Si en plus le programme possède des privilèges, les résultats peuvent être assez catastrophiques. Ainsi décrit, le problème semble complexe : il faudrait que l'attaquant puisse insérer dans une zone mémoire de l'application un bout de code qu'il a lui-même écrit, et qu'il arrive à écrire l'adresse de ce bout de code là où le système s'attend à retrouver l'adresse de retour de la fonction. Cependant, ce genre d'attaque est aujourd'hui "très" courant, et les applications présentant ce genre d'erreur deviennent très rapidement la cible d'attaques.
Voici un exemple très classique, où ce genre d' "exploit" peut arriver :
int traite_chaine(const char *ma_chaine)
 char tmp[512];
 strcpy(tmp, ma_chaine);
Ce code, hélas plus fréquent qu'on ne le pense, est à bannir. Parmi différentes méthodes, on peut éviter ce problème en ne copiant qu'un certain nombre des premiers caractères de la chaîne, avec la fonction codice_16 :
char tmp[512];
strncpy(tmp, ma_chaine, sizeof(tmp));
tmp[sizeof(tmp) - 1] = '\0';
On notera l'ajout explicite du caractère nul, si "ma_chaine" est plus grande que la zone mémoire "tmp". La fonction codice_16 ne rajoutant hélas pas, dans ce cas, de caractère nul. C'est un problème tellement classique que toute application C reprogramme en général la fonction codice_16 pour prendre en compte ce cas de figure (voir la fonction POSIX codice_19, ou codice_20 utilisée par le système d'exploitation OpenBSD, par exemple)
La limitation d'une chaîne à un nombre d'octets pose par contre des problèmes dans le cas des caractères multi-octets qui risquent alors e ne pas être entiers.
Le langage n'offrant que très peu d'aide, la gestion correcte des chaînes de caractères est un problème à ne pas sous-estimer en C.
Recherche dans une chaîne.
char * strchr(const char * chaine, int caractère);
char * strrchr(const char * chaine, int caractère);
Recherche le "caractère" dans la "chaine" et renvoie la position de la première occurrence dans le cas de codice_21 et la position de la dernière occurrence dans le cas de codice_22.
char * strstr(const char * meule_de_foin, const char * aiguille);
Recherche l"'aiguille" dans la "meule de foin" et renvoie la position de la première occurrence.
Nota: Dans la cas de caractères multi-octets, cette fonction ne cherche qu'un morceau de caractère .
Traitement des blocs mémoire.
La bibliothèque codice_2 contient encore quelques fonctions pour la manipulation de zone brute de mémoire. Ces fonctions, préfixées par codice_24, sont les équivalents des fonctions codice_25 pour des zones de mémoire qui ne sont pas des chaînes de caractères. Cela peut être des tableaux non terminés par la valeur 0, comme des tableaux pouvant contenir la valeur 0 avant la fin, par exemple. Elles permettent aussi de traiter des structures (par exemple pour copier les données d'une structure dans une autre), ou des zones de mémoires allouées dynamiquement (par exemple pour initialiser une zone mémoire allouée par codice_26).
Comme, au contraire des fonctions codice_25, elles n'ont pas de délimiteur de fin de tableau, il faut leur donner en paramètre la taille de ces tableaux (cette taille étant en "bytes" au sens strict, bien que souvent en octets).
void memcpy( void * destination, const void * source, size_t longueur );
Copie 'codice_28' octet(s) de la zone mémoire 'codice_29' dans 'codice_30'. Vous devez bien sûr vous assurer que la chaine destination ait suffisamment de place, ce qui est en général plus simple dans la mesure où l'on connait la longueur.Attention au recouvrement des zones : si codice_31 alors codice_32.
void memmove( void * destination, const void * source, size_t longueur )
Identique à la fonction codice_33, mais permet de s'affranchir totalement de la limitation de recouvrement.
void memset( void * memoire, int caractere, size_t longueur );
Initialise les 'codice_28' premiers octets du bloc 'codice_35', par la valeur convertie en type codice_36 de 'codice_37'. Cette fonction est souvent employée pour mettre à zéro tous les champs d'une structure ou d'un tableau :
struct MonType_t mem;
memset( &mem, 0, sizeof mem );
int memcmp( const void * mem1, const void * mem2, size_t longueur );
Compare les 'codice_28' premiers octets des blocs 'codice_39' et 'codice_40'. Renvoie les codes suivants :
void * memchr( const void * memoire, int caractere, size_t longueur );
Recherche dans les 'codice_28' premiers octets du bloc 'codice_35', la valeur convertie en type codice_36 de 'codice_37'. Renvoie un pointeur sur l'emplacement où le caractère a été trouvé, ou codice_51 si rien n'a été trouvé.
void * memrchr( const void * memoire, int caractere, size_t longueur );
Pareil que codice_52, mais commence par la fin du bloc 'codice_35'.