Aller au contenu

Programmation C source/chaînes

Un livre de Wikilivres.
Programmation C source
Programmation C++
Programmation C++
Sommaire
Modifier ce modèle
#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;
}