Programmation C source/chaînes
Apparence
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h> /* malloc */
int main(void)
{
/*
Les fonctions permettant de manipuler les chaînes de caractères
1. Comparaison de chaînes
2. Longueur d'une chaîne
3. Concaténation et copie de chaînes
4. Recherche dans une chaîne
5. Traitement des blocs mémoire
*/
printf("----------------------------------------\n");
printf(" Comparaison de chaînes\n");
printf("----------------------------------------\n");
#if 0
int strcmp(const char * chaine1, const char * chaine2);
int strncmp(const char * chaine1, const char * chaine2, size_t longueur);
#endif
/* La fonction strcmp() compare les deux chaînes
La fonction strncmp() est identique sauf qu’elle ne compare
que les n (au plus) premiers caractères
négatif, si chaine1 est inférieure à chaine2
nul, si chaine1 est égale à chaine2
positif, si chaine1 est supérieur à chaine2
Pour tester l'égalité entre deux chaînes, il faut donc écrire
if (strcmp(chaine1, chaine2) == 0) ...
ou if (!strcmp(chaine1, chaine2)) ...
if (strcmp(chaine1, chaine2)) ... teste si deux chaînes sont différentes
Noter chaine1 == chaine2, si chaine1 et chaine2 sont des char *
revient à tester si les deux chaînes pointent sur la même zone mémoire */
#if 0
int strcasecmp(const char * chaine1, const char * chaine2);
int strncasecmp(const char * chaine1, const char * chaine2, size_t longueur);
#endif
/* La fonction strcasecmp() compare deux chaînes,
en ignorant les différences entre majuscules et minuscules.
La fonction strncasecmp() est similaire, à la différence qu’elle ne
prend en compte que les n premiers caractères */
// exemples :
if (!strcmp("Jean", "Jean-pierre"))
printf("chaînes identiques\n");
else
printf("chaînes différentes\n");
if (!strncmp("Jean", "Jean-pierre", 4))
printf("4 premiers caractères identiques\n");
else
printf("4 premiers caractères différents\n");
if (!strcasecmp("pierre", "PIERRE"))
printf("chaînes identiques\n");
else
printf("chaînes différentes\n");
printf("\n----------------------------------------\n");
printf(" Longueur d'une chaîne\n");
printf("----------------------------------------\n");
#if 0
size_t strlen(const char * chaine);
#endif
// Renvoie la longueur de la chaine sans compter le caractère '\0'.
char chaine1[] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r', '\0' };
printf("Longueur de chaine1(Bonjour) = %zu\n", strlen(chaine1));
printf("\n----------------------------------------\n");
printf(" Concaténation et copie de chaînes\n");
printf("----------------------------------------\n");
#if 0
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);
#endif
/* strcpy copie le contenu de source à l'adresse mémoire pointé par destination,
incluant le caractère nul final
strcat 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
si la chaîne source a moins de n caractères non nuls,
strncpy rajoutera n - strlen(source) caractères nuls à la suite, pour compléter
si la chaîne source fait au moins n caractères, la fonction n'insèrera pas de
caractère nul en fin de chaine (i.e. destination ne sera pas une chaîne de
caractères valide) */
#if 0
char * copie_chaine(const char * source)
{
char * chaine = malloc(strlen(source));
if (chaine != NULL)
{
strcpy(chaine, source);
}
return chaine;
}
#endif
/* 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 : */
#if 0
char * chaine = malloc( strlen(source) + 1 );
#endif
/* 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 : */
#if 0
int traite_chaine(const char *ma_chaine)
{
char tmp[512];
strcpy(tmp, ma_chaine);
/* ... */
}
#endif
/* 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 strncpy : */
#if 0
char tmp[512];
strncpy(tmp, ma_chaine, sizeof(tmp));
tmp[sizeof(tmp) - 1] = '\0';
#endif
/* On notera l'ajout explicite du caractère nul, si ma_chaine est plus grande que
la zone mémoire tmp. La fonction strncpy 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 strncpy pour prendre en compte ce cas de
figure (voir la fonction POSIX strdup, ou strlcopy utilisée par le système
d'exploitation OpenBSD, par exemple)
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. */
char chaineSource[] = { 'P', 'i', 'e', 'r', 'r', 'e', '\0' };
char * chaineDestination = malloc( strlen(chaineSource) + 1 );
size_t longueurDestination = strlen(chaineSource) + 1;
strcpy(chaineDestination, chaineSource);
printf("%s\n", chaineSource);
printf("(strcpy) %s\n", chaineDestination);
int n = 3;
strncpy(chaineDestination, chaineSource, n);
if (n > 0)
chaineDestination[n - 1]= '\0';
printf("\n%s\n", chaineSource);
printf("(strncpy) %s\n", chaineDestination);
const char * chaineCat = "err";
if (strlen(chaineDestination)+strlen(chaineCat)+1 <= longueurDestination)
strcat(chaineDestination, chaineCat);
printf("\n%s\n", chaineCat);
printf("(strcat) %s\n", chaineDestination);
if (strlen(chaineDestination)+1+1 <= longueurDestination)
strncat(chaineDestination, chaineCat, 1);
printf("(strncat 1) %s\n", chaineDestination);
char * chaineCopie = strdup(chaineSource);
printf("\nchaineSource %s\n", chaineSource);
printf("(strdup) chaineCopie %s\n",chaineCopie);
free(chaineCopie);
//strlcopy(); OpenBSD
free(chaineDestination);
printf("\n----------------------------------------\n");
printf(" Recherche dans une chaîne\n");
printf("----------------------------------------\n");
#if 0
char * strchr(const char * chaine, int caractère);
char * strrchr(const char * chaine, int caractère);
#endif
/* Recherche le caractère dans la chaine et renvoie la position de la première
occurrence dans le cas de strchr et la position de la dernière occurrence dans le
cas de strrchr. */
#if 0
char * strstr(const char * meule_de_foin, const char * aiguille);
#endif
/* Recherche l'aiguille dans la meule de foin et renvoie la position de la première
occurrence. */
const char * chaineRecherche = "Recherche";
char * position = strchr(chaineRecherche, 'e');
printf("*position = %c\n", *position);
printf("position = %p\n", position);
position = strrchr(chaineRecherche, 'e');
printf("\n*position = %c\n", *position);
printf("position = %p\n", position);
char * positionAiguille = strstr(chaineRecherche, "che");
printf("\npostitionAiguille = %s\n", positionAiguille);
printf("\n----------------------------------------\n");
printf(" Traitement des blocs mémoire\n");
printf("----------------------------------------\n");
#if 0
void * memcpy( void * destination, const void * source, size_t longueur );
#endif
/* Copie 'longueur' octet(s) de la zone mémoire 'source' dans 'destination'.
Vous devez bien sûr vous assurer que la chaine destination ait suffisament de
place, ce qui est en général plus simple dans la mesure où l'on connait la
longueur.
Attention au récouvrement des zones :
si destination + longueur < source alors source >= destination. */
#if 0
void * memmove( void * destination, const void * source, size_t longueur );
#endif
/* Identique à la fonction memcpy(), mais permet de s'affranchir totalement
de la limitation de recouvrement. */
#if 0
void * memset( void * memoire, int caractere, size_t longueur );
#endif
/* Initialise les 'longueur' premiers octets du bloc 'memoire', par la valeur
convertie en type char de 'caractere'. Cette fonction est souvent employée pour
mettre à zéro tous les champs d'une structure ou d'un tableau : */
#if 0
struct MonType_t mem;
memset( &mem, 0, sizeof mem );
#endif
#if 0
int memcmp( const void * mem1, const void * mem2, size_t longueur );
#endif
/* Compare les 'longueur' premiers octets des blocs 'mem1' et 'mem2'. Renvoie les
codes suivants :
< 0 : mem1 < mem2
= 0 : mem1 == mem2
> 0 : mem1 > mem2 */
#if 0
void * memchr( const void * memoire, int caractere, size_t longueur );
#endif
/* Recherche dans les 'longueur' premiers octets du bloc 'memoire', la valeur
convertie en type char de 'caractere'. Renvoie un pointeur sur l'emplacement où
le caractère a été trouvé, ou NULL si rien n'a été trouvé. */
#if 0
void * memrchr( const void * memoire, int caractere, size_t longueur );
#endif
/* Pareil que memchr(), mais commence par la fin du bloc 'memoire'. */
const char * maSource1 = "abcDEFghi";
int longueur1 = strlen(maSource1) + 1;
char * maDestination1 = malloc(longueur1);
if (maDestination1 + longueur1 < maSource1 || maSource1 + longueur1 < maDestination1)
{
memcpy(maDestination1, maSource1, longueur1);
printf("(memcpy) maDestination1 = %s\n", maDestination1);
}
else
{
printf("Les deux zones ne doivent pas se chevaucher.");
}
free(maDestination1);
char maDestination2[] = "abcDEFghi";
char * maSource2 = &maDestination2[3];
memmove(maDestination2, maSource2, 7);
printf("\n(memmove) maDestination2 = %s\n", maDestination2);
memset(maDestination2, '*', 6);
printf("\n(memset) maDestination2 = %s\n", maDestination2);
char * chaineA = "bonjour";
char * chaineB = "Bonjour";
if (memcmp(chaineA, chaineB, 7))
printf("\n(memcmp) les 7 premiers octets sont différents.\n");
char * caractere = memchr(chaineB, 'o', 3);
printf("\n(memchr) caractere %c = %p\n", *caractere, caractere);
caractere = memrchr(chaineB, 'o', 7);
printf("\n(memrchr) caractere %c = %p\n", *caractere, caractere);
return 0;
}