Accueil de la Caverne Informatique - L'assembleur

Le langage C

Introduction

Le C est l'un des langages de programmation les plus utilisés au monde. Langage d'UNIX par excellence (les UNIX sont écrits presqu'entièrement en C), il est standardisé (norme ANSI) et dispose de très nombreuses bibliothèques. Disposant d'un compilateur plutôt complaisant, le C permet des raccourcis syntaxiques qui ont contribué à son succès.

Cette page vous propose tout d'abord une courte introduction au C, afin de présenter les spécificités de ce langage. Puis, quelques exemples qui illustrent ces spécificités. Enfin, vous aurez la possibilité de vous entraîner en résolvant des exercices, sous le contrôle de la Moulinette.

Hello, World !

Commençons par le classique Hello World qui consiste à afficher un message simple à l'écran. L'instruction #include indique les bibliothèques à charger - ici, la bibliothèque standard stdio.h. Celle-ci permet notamment l'usage de printf lequel, vous l'avez deviné, affiche une chaîne de caractères à l'écran. Le point d'entrée de tout programme C est la procédure main().

#include <stdio.h>

void main()
{
	printf( "Bonjour, le monde !" );
}

Le mot-clé void indique que main() est une procédure, et non une fonction (elle ne renverra pas de valeur de retour).

[haut de page]

Les déclarations des types simples

Avant d'utiliser une variabl, il faut la déclarer, c'est-à-dire indiquer son nom et son type, plus éventuellement une valeur initiale. Les déclarations se font toujours au début d'un bloc { ... } (ou à l'extérieur de toute procédure et fonction, pour les variables globales), et toujours avant toute instruction.

Ce n'est pas bien :
void main()
{
	procedure_truc();
	chiffre = fonction_truc( entier );

	int entier = 5;
	int chiffre;

	if( chiffre == 5 )
		chiffre = fonction_truc( toto );

	int toto = 28;
}
C'est bien :
int chiffre;

void main()
{
	int entier = 5;

	procedure_truc();
	chiffre = fonction_truc( entier );

	if( chiffre == 5 )
	{
		int toto = 28;
		chiffre = fonction_truc( toto );
	}
}

Les caractères

char caractere = 'a';
char c = 32;
char zzz;

Les caractères sont codés sur 8 bits (1 octet). Pour les manipuler, on entoure le caractère d'une simple quote 'a'. La double quote "a" représente une chaîne de caractères (le caractère 'a' suivi du symbole (invisible) de fin de chaîne). Il est également possible de les désigner sous leur code ASCII (un entier compris entre 0 et 255).

On peut donner une valeur par défaut à la variable que l'on déclare, c'est même recommandé. Ainsi, dans les déclarations précédentes, caractere sera initialisé à 'a' et c sera initialisé à ' ' (caractère espace, code ASCII 32). Au contraire, zzz n'a pas été initialisé et a donc une valeur indéfinie pour le moment.

Astuce utile : On a la possibilité de déclarer plusieurs variables du même type en les séparant par une virgule, comme ceci :

char a, b = 32, c = 'Z', d, e = 'a';

Les entiers

int a, b;
int i = -2, j = -8;
unsigned int j = 35;
short int z = 3;
unsigned short int coucou = 12;

Il existe plusieurs types d'entiers. Les int (integer) sont des entiers signés 32 bits (ils peuvent prendre toute valeur comprise entre environ -2 milliard et + 2 milliard (entre -2.147.483.648 et 2.147.483.647 exactement). Les unsigned int sont des entiers positifs qui peuvent prendre toute valeur comprise entre 0 et 4 milliards (4.294.967.295 exactement).

Les short int sont des entiers 16 bits dont la plage de valeurs s'étend de -32.768 à 32.767. Les unsigned short int vont de 0 à 65535.

Les pointeurs

Les pointeurs sont des variables qui contiennent l'adresse d'une donnée, d'une variable, d'une procédure. Ils utilisent deux opérateurs :

Voici deux exemples de déclaration de pointeur :

char *c;
int *i = NULL;

NULL vaut 0 et signifie que le pointeur ne pointe sur rien. Le pointeur i est initialisé à NULL, il ne contient pas d'adresse ; pour l'utiliser, il faudra lui affecter une adresse valide. Le pointeur c n'est pas initialisé. Sa valeur est indéfinie, théoriquement il peut contenir n'importe quelle valeur (correcte ou incorrecte). Il est fortement conseillé de l'initialiser.

On peut interpréter la déclaration des pointeurs de deux manières différentes :

Voici un exemple d'utilisation des pointeurs :

int *pointeur = NULL;
int entier = 250;

// Le pointeur prend l'adresse de l'entier.
pointeur = &entier;

// La valeur située à l'adresse du pointeur (c'est-à-dire l'entier)
// se voit multiplier par 4 et additionner 1.
*pointeur = *pointeur * 4 + 1;

// Le test suivant sera donc toujours vrai
if( (*pointeur == entier) && (entier == 1001) )
{
	printf( "Toujours Vrai." );
}
else
{
	printf( "Ce message ne s'affichera jamais." );
}

Les tableaux

int tab[];
int tableau[10] = {0,1,2,3,4,5,6,7,8,9};
char chaine[6] = {'c', 'o', 'u', 'c', 'o', 'u'};
char string[] = "Une chaine de caractères est un tableau de caractères";

On peut déclarer des tableaux de n'importe quel type (entier, caractères, ...). On peut déclarer sa taille (tableau statique, ou bien ne pas la déclarer et l'allouer dynamiquement. L'index des tableaux commence à 0 et va jusqu'à taillemax - 1.

Une chaîne de caractères est un tableau de caractères suivi d'un 0. Il est possible d'accéder à n'importe quel caractère d'une chaîne en indiquant son indice ; par exemple : chaine[ 10 ] = 'e' signifie que le 11ème caractère, d'indice 10, sera remplacé par 'e'. Pour avoir un exemple de manipulation de chaîne, cliquez ici.

Les tableaux sont des pointeurs. Lorsque vous déclarez un tableau de nom le_tableau, l'identifiant le_tableau est un pointeur. La seule différence entre un pointeur normalement déclaré et un tableau est que l'identifiant du tableau pointe sur une zone mémoire allouée statiquement et initialisée, tandis que le pointeur ne pointe sur rien. Par conséquent, la manipulation suivante est valide :

int *pointeur = NULL;
int tableau[ 3 ] = { 10, 20, 30 };

pointeur = tableau;

pointeur[ 0 ] ++;
pointeur[ 1 ] ++;
pointeur[ 2 ] ++;

Vous le devinez sans peine, une fois ce code exécuté, pointeur et tableau pointent vers la même zone mémoire qui contient désormais { 11, 21, 31 }.

[haut de page]

Les opérateurs

Arithmétique

Voici une opération arithmétique dont le résultat est placé dans la variable entière var (le résultat de l'opération est 10).

int var = -5.2 * ( -2.8 + 9/8 ) + 3/2 - 7/8;

Affectation, incrémentation, décrémentation

Comparaison

Logique

if( ! temperature_inconnue )
{
	if( temperature > 15 && temperature < 25 )
	{
		printf( "La température me convient\n" );
	}

	if( temperature < 0 || temperature > 40 )
	{
		printf( "Mais qu'est-ce que c'est que ce temps ?" );
	}
}

Les opérateurs && et || sont évalués de la gauche vers la droite. Dans l'expression if( temperature > 15 && temperature < 25 ), le programme teste tout d'abord temperature > 15. Si le résultat est faux (la température est inférieure ou égale à 15), le second test n'est pas évalué car l'expression ne pourra jamais être vraie (Faux ET Vrai donne Faux, Faux ET Faux donne Faux). Si le résultat est vrai, le second test est évalué.

De même, dans l'expression if( temperature < 0 || temperature > 40 ), si le premier test est vrai (temperature < 0), le second n'est pas évalué, car Vrai OU expression donne toujours VRAI.

Pointeurs

[haut de page]

Les structures du C

Structures conditionnnelles

// Si ... Alors ... Sinon ...
if( variable == valeur )
{
	instruction1();
	instruction2();
	...
}
else
{
	instruction3();
	instruction4();
}

// Selon Cas ... Cas variable==0 ... Cas variable==1 ... Autres cas
switch( variable )
{
    case 0:
	instruction1();
	break;

    case 1:
	instruction2();
	instruction3();
	break;

    default:
	instruction4();
	break;
}

Boucles

// Tant que... Faire ...
while( variable < valeur )
{
	instruction1();
	instruction2();
	variable ++;
}


// Faire ... Tant Que ...
do
{
	instruction1();
	instruction2();
	instruction3();
	instruction4();
	variable = fonction1();
}
while( variable != valeur );
[haut de page]

Les déclarations des types avancés

Les énumérations

Les énumérations sont des variables qui peuvent prendre un nombre limité de valeurs, que l'on énumère. Chaque valeur est nommée par un identifiant. On déclare une énumération par le mot-clef enum, suivi du nom de l'énumération (peut être omis), suivi de la liste des valeurs disponibles, suivie d'une déclaration d'une ou plusieurs variables.

void proc()
{
	enum vingt_quatre_heures
	{
		MATIN,
		MIDI,
		APRES_MIDI,
		SOIR,
		NUIT
	}
	aujourdhui, tout_a_l_heure;

	if( ... )
	{
		aujourdhui = APRES_MIDI;
		tout_a_l_heure = SOIR;
	}
	else
		...

	switch( aujourdhui )
	{
		...
	}
}

Au niveau du compilateur, chaque valeur est représentée par un entier. Ainsi, MATIN vaut 0, MIDI vaut 1, etc... Cependant, pour des raisons de clarté de code, il est recommandé de ne jamais mélanger les deux notations (ne jamais faire aujourdhui = 2; ou bien toto = MIDI + SOIR * NUIT).

Voici une possibilité d'utilisation équivalente à la précédente. La déclaration de l'énumération se fait cette fois en dehors de la procédure, tandis que la variable est déclarée à l'intérieur. On décide de donner explicitement des valeurs entières aux composantes de notre énumération.

enum vingt_quatre_heures
{
	MATIN = 0,
	MIDI = 1,
	APRES_MIDI = 2,
	SOIR = 3,
	NUIT = 4
};

void proc()
{
	enum vingt_quatre_heures aujourdhui, tout_a_l_heure;

	if( ... )
	{
		aujourdhui = APRES_MIDI;
		tout_a_l_heure = SOIR;
	}
	else
		...
	...
}

Les structures

Les structures regroupent plusieurs variables de n'importe quel type sous un seul et même identifiant. Ces regroupements sont généralement d'ordre sémantique : la structure peut être vue comme le contenant, le tout, et ses membres comme le contenu, les parties, les composants, les éléments... La structure peut faire penser à la notion d'objet que l'on retrouve dans certains autres langages comme Java.

Voici un petit exemple d'utilisation des structures.

struct Personne
{
	char *prenom;
	char *nom;
	unsigned int age;

	enum
	{
		MASCULIN,
		FEMININ
	}
	sexe;
};

void afficher( struct Personne * );

void main()
{
	struct Personne moi;

	moi.prenom = "Eric";
	moi.nom = "Minso";
	moi.age = 23;
	moi.sexe = MASCULIN;

	afficher( &moi );
}

void afficher( struct Personne *quelquun )
{
	printf( "Identité : %s %s\n", quelquun->prenom, quelquun->nom );
	printf( "Age : %d ans\n", quelquun->age );
	printf( "Sexe : %s\n", (quelquun->sexe == MASCULIN)?"masculin":"féminin" );
}

Que signifie le "." ? C'est l'opérateur qui permet d'accéder aux membres de la structure à partir de son nom. Il est en effet impossible d'accéder individuellement aux membres d'une structure sans nommer la structure elle-même. En revanche, cette petite manipulation basée sur les pointeurs (sortez vos aspirines) peut être une alternative :

void truc( struct Personne *personne )
{
	char *ze_nom, *ze_prenom;
	int *ze_age;

	ze_prenom = personne->prenom;
	ze_nom = personne->nom;
	ze_age = &( personne->age );
	...
}

Que signifie "->" ? Lorsque l'identifiant désigne un pointeur de structure et non une structure, on ne peut pas utiliser le "." ; -> permet donc d'accéder aux membres de la structure à partir de son pointeur. Ainsi, les 4 notations suivantes sont parfaitement équivalentes :

Vous noterez l'importance des parenthèses afin de faire respecter la priorité des opérateurs * et & sur . et ->.

Pourquoi écrit-on ze_age = &(personne->age) tandis qu'on écrit ze_nom = personne->nom ? ze_nom et personne->nom sont de même type, ce sont des pointeurs. La structure personne ne contient pas physiquement la chaîne de caractères, seulement son pointeur. Ainsi, lorsqu'on écrit ze_nom = personne->nom;, on copie un pointeur, on fait pointer ze_nom vers la chaîne déjà pointée par personne->nom;.

En ce qui concerne personne->age, la structure personne contient physiquement un entier nommé "age". ze_age est un pointeur d'entier, on lui affecte l'adresse de l'entier, c'est-à-dire l'adresse de personne->age, grâce à l'opérateur d'adresses &. Pour des raisons de priorité des opérateurs, on fait usage de parenthèses : ze_age = &( personne->age );

[haut de page]

Entrées / sorties avec printf et scanf

Le C ne dispose pas de fonctions d'interface graphique en standard (une interface graphique est spécifique à un système d'exploitation). Il existe par contre des fonctions d'accès à la console et aux fichiers.

Console

La fonction printf permet d'afficher du texte formaté. C'est une des rares fonctions dont les paramètres sont variables. Son prototype, défini dans stdio.h, est :

int printf( const char *format, ... );

Le premier paramètre est une chaîne de caractère contenant aucun, un ou plusieurs codes de formatage. Le mot-clef const sigifie que la chaîne ne sera pas modifiée par la fonction. Les trois points ... signifient que le nombre de paramètres est variable. Voici un exemple d'utilisation :

char caractere = 'z';
printf( "Le caractère est %c\n", caractere );

Le code de formatage %c commande l'affichage d'un caractère. Le caractère à afficher suit immédiatement dans la liste des paramètres. Le code de formatage \n déclenche un saut de ligne. L'instruction ci-dessus affichera donc Le caractère est z. Voici comment afficher une chaîne ou un entier :

char *chaine = "toto";
int chiffre = 15;

printf( "La chaîne est %s\n", chaine );
printf( "Le chiffre est %d\n", chiffre );

Voici les codes pour afficher la plupart des variables :

Voici quelques autres codes de formatage divers :

La fonction printf affiche chaines de caractères et variables. La fonction scanf lit le clavier et place le résultat dans la variable passée en paramètres (par adresse). Voici comment utiliser scanf :

char c = 0;
char *chaine = "";
int chiffre = 0;


printf( "Entrez un nouveau caractère :\n" );
scanf( "%c", &c );
printf( "Entrez une nouvelle chaîne :\n" );
scanf( "%s", chaine );
printf( "Entrez un nouveau chiffre :\n" );
scanf( "%d", &chiffre );

Vous aurez noté qu'il n'y a pas de & devant chaine. En effet, chaine désigne déjà un pointeur de caractère(s). On place le fameux & devant c et chiffre afin de transmettre des pointeurs sur ces variables.

Fichiers

Il est possible d'utiliser des variantes de printf et scanf sur des fichiers ouverts : ces variantes sont fprintf et fscanf.

Il existe plusieurs autres fonctions d'accès aux fichiers (fread et fwrite, fgets et fputs, ...) mais ce n'est pas le but de cette page d'en faire un comparatif détaillé. Voici un court exemple d'utilisation de fichiers.

char *mon_nom = "Eric";
FILE *fichier = fopen( "toto.txt", "w" );

fprintf( fichier, "Mon nom est %s\n", mon_nom );
fclose( fichier );
[haut de page]

Le passage de paramètres

Le passage de paramètres permet de transmettre des données depuis une fonction appelante vers une fonction appelée. Il existe deux types de transfert :

Par valeur (ou par copie)

La fonction appelante transmet une variable à la fonction appelée. Si celle-ci modifie la valeur de la variable, les changements ne seront pas répercutés dans la fonction appelante.

#include <stdio.h>

// La fonction toto reçoit un paramètre : "chiffre"
void toto( int chiffre )
{
	// La fonction toto modifie le paramètre chiffre
	chiffre = 2 * chiffre + 25;
	printf( "chiffre = %d\n", chiffre );
}

void main()
{
	int i = 50;

	// On transmet la valeur de la variable i
	toto( i );

	// i vaut 50 : la variable n'a pas été modifiée par toto()
	printf( "chiffre = %d\n", i );
}

En réalité, ce n'est pas la variable qui est transmise, mais bien sa valeur qui est copiée. Cela signifie que les variables de la fonction appelante ne seront pas modifiées quelles que soient les modifications opérées dans la fonction appelée.

Normalement, seuls les types simples, de petite taille, (entiers, flottants, caractères, énumérations, pointeurs) peuvent être transmis par valeur. Il n'est généralement pas possible de transmettre par valeur les types gourmands en mémoire, tels que tableaux, chaînes de caractères, structures...

Toutefois, les compilateurs récents autorisent le passage par valeur de tableaux de taille fixe ou de structures.

Par adresse

Lors du passage par adresse, toute modification du paramètre dans la fonction appelée est répercutée dans la fonction appelante.

#include <stdio.h>

// La fonction tutu reçoit un paramètre : "*chiffre"
// "*chiffre" représente un entier
// "chiffre" représente le pointeur sur cet entier
void tutu( int *chiffre )
{
	(*chiffre) = 2 * (*chiffre) + 25;
	printf( "chiffre = %d\n", *chiffre );
}

void main()
{
	int i = 50;

	// On transmet l'adresse de la variable i
	tutu( &i );

	// i vaut 125 : la variable a été modifiée par tutu()
	printf( "chiffre = %d\n", i );
}

La variable i est transmise par adresse. Par conséquent, toutes les modifications effectuées sur le paramètre *chiffre à l'intérieur de la procédure tutu affecteront i de la procédure main.

Pour vous donner mal à la tête : en réalité, un passage par adresse d'une variable correspond au passage par valeur d'un pointeur sur cette variable. Dans l'exemple ci-dessus, l'adresse de la variable, "&i", est bien transmise par valeur.

Seul un passage par adresse permet de transmettre un tableau, une chaîne de caractères ou une structure, car ce passage équivaut à la transmission par valeur de pointeurs sur un tableau, une chaîne de caractères, une structure...

Ne faîtes jamais ça

Quelques expériences dangereuses.

Variables non initialisées
int i;
char *moi = "bonjour !!";

void pas_bien()
{
	printf( "Caractère %d : %c\n", i, moi[ i ] );
}

La variable i n'est pas initialisée, le résultat est imprévisible... et conduira probablement au plantage.

Comportements indéfinis
int i = 1;
char *moi = "bonjour !!";

void pas_bien()
{
	moi[ i ] = i ++;
	i = i ++ * i ++;
}

Le sens dans lequel ces expressions sont évaluées n'est pas officiellement défini. Est-ce le i de moi[i] qui est d'abord évalué ou bien l'expression i ++ ? Le résultat dépend de chaque compilateur.

Erreurs de priorité
void pas_bien( int *index, int limite )
{
	while( *index < limite )
		*index ++;
}

En écrivant ceci, on pense sans doute innocemment que c'est l'entier qui sera incrémenté. En réalité, ++ est prioritaire sur * et c'est le pointeur qui sera incrémenté. Cela sigifie que, entre guillemets, index ne pointera plus sur *indexinitial.

Afin de rectifier ce problème, l'usage des parenthèses s'impose : ( *index )++;

[Haut de page]


Accueil de la Caverne Informatique - L'assembleur
Accueil de la Caverne Informatique - L'assembleur

Le langage C : Applications

Exemple de programmes C

La taille d'une chaîne de caractère

Voici une fonction très courte pour renvoyer la longueur d'une chaîne de caractère. C'est à peu de choses près ce que fait la fonction C strlen.

int longueur( char *chaine )
{
	int index = 0;

	while( chaine[ index ++ ] );

	return index;
}

Pour les personnes ayant encore quelques difficultés avec les raccourcis syntaxiques du C, en voici une version "longue" équivalente.

int longueur( char *chaine )
{
	int index = 0;

	while( chaine[ index ] != 0 )
	{
		index = index + 1;
	}

	return index;
}
[haut de page]

L'année bissextile

#include <stdio.h>

void main()
{
	// Déclaration de la variable de type entier 'annee'
	int annee;

	// On affiche un message d'invite
	printf("Entrez l'année :");

	// La fonction 'scanf' va lire un entier et placer la valeur
	// lue à l'adresse de 'annee'
	scanf("%d", &annee);

	// Une année bissextile est divisible par 4
	// mais pas par 100 sauf par 400
	if (annee % 4 == 0 && annee % 100 != 0 ||  annee % 400 == 0)
	{
		// La fonction 'printf' remplacera %d par la valeur de 'annee'
		// et affichera le tout à l'écran
		printf("%d est bien une année bissextile\n", annee);
	}
	else
	{
		printf("%d n'est pas bissextile...\n", annee);
	}
}
[haut de page]

Une permutation

#include <stdio.h>

// Définition de la procédure 'permut' qui prend en paramètre
// trois pointeurs d'entiers
void permut(int *var1, int *var2, int *var3)
{
	// Définition d'une variable de type 'pointeur d'entier'
	// var4 représente le pointeur, et *var4 représente l'entier pointé
	int *var4;

// ATTENTION ! Ici, var1, var2, var3, var4 représentent des pointeurs
// et *var1, *var2, *var3, *var4 représentent les variables elles-mêmes.

	// La variable '*var4' prend la valeur de *var3... etc...
	*var4 = *var3;
	*var3 = *var2;
	*var2 = *var1;
	*var1 = *var4;
}


void main()
{
	// Définition de trois variables de type entier...
	int var1;
	int var2;
	int var3;

	// Affichage de l'invite, puis lecture des variables
	printf("Donnez les variables :\n");
	printf("la 1 :");
	scanf("%d", &var1);
	printf("\n la 2 :");
	scanf("%d", &var2);
	printf("\n la 3 :");
	scanf("%d", &var3);

	// On envoie à la fonction 'permut' les adresses des variables
	permut(&var1, &var2, &var3);
	// Le passage par adresses a modifié la valeur des variables

	printf("Voilà, c'est fait.\n");
	printf("var1 : %d\n", var1);
	printf("var2 : %d\n", var2);
	printf("var3 : %d\n", var3);
}
[haut de page]

Les tableaux

#include <stdio.h>

void main()
{
	int i;
	int nb=0;
	char c='a';
	char tab[100];

	printf("Donnez la phrase que vous voulez renverser suivis de $ \n");

	// Tant que la variable c n'est pas égale à '$'
	while ( c != '$' )
	{
		c = getchar();

		// On place le caractère c dans un tableau, à l'indice nb
		tab[nb] = c;

		// On incrémente l'indice du tableau
		nb ++;
	}

	printf("\n\t Ben, à l'envers c'est :\n");
	nb --;

	// On parcourt le tableau à l'envers
	for (i = nb; i >= 0; i--)
	{
		// On affiche le caractère correspondant
		printf("%c", tab[i]);
	}

	printf("\n");
}
[haut de page]

Les arguments

#include <stdio.h>

// La procédure principale prend en paramètre argc le nombre d'arguments
// passés par la ligne de commande (dont le nom du programme lui-même), et
// argv qui est un tableau de chaînes de caractères.

void main(int argc, char *argv[])
{
	int i;

	// On ne compte pas le programme lui-même
	printf("Nombre d'arguments : %d", argc-1);

	for( i=0; i<argc; i++ )
	{
		// On affiche tous les arguments de la ligne de commande
		printf("Argument %d : %s", argc, argv[i];
	}
}
[haut de page]

Une pile

Attention, on va faire un peu plus compliqué maintenant : le programme suivant est un classique en algorithmique : la programmation d'une pile permettant d'empiler, de dépiler et d'afficher des valeurs.

Les nouveautés : les déclarations de structures (permettant de regrouper des informations) et l'allocation dynamique de mémoire avec la fonction malloc (qui réclame au système d'exploitation une nouvelle zone mémoire. S'il n'y en a pas, malloc retourne NULL. S'il y en a, malloc retourne un pointeur sur cette zone.

Si vous êtes étudiant et devez rendre un TP sur les piles, ne copiez pas bêtement ce programme, essayez d'abord de le comprendre, c'est ainsi qu'on progresse.

Dans le cas où je n'aurai pas su être convaincant, ce source contient quelques caractères parasites, invisibles à l'œil nu, mais qui empêcheront une compilation sans un petit débogage préalable :-)

#include <stdio.h>
#define MAX_ELEMENTS_PILE 5

/* Définition du type 'une case de la pile' avec dedans : */
/* la valeur : Val */
/* et l'adresse de la valeur suivante : Svt */
/* Si il n'y a pas de case suivante, alors Svt vaut NULL */

struct T_Element_Pile
{
	int Val;
	struct T_Element_Pile *Svt;
};

/* Définition du type 'la pile' avec dedans : */
/* Le nombre Max d'éléments */
/* Le pointeur 'Sommet' de la pile */

struct T_Pile
{
	int Max;
	struct T_Element_Pile *Sommet;
};

/* Message d'erreur standard */

void Erreur()
{
	printf("\n\t ERREUR !!\n\n");
	printf("\n Soit vous souhaitez vider une pile vide,\n");
	printf("\n Soit vous tentez de remplir une pile pleine\n");
}

/* Message de confirmation standard */

void Ok()
{
	printf("\n L'opération est effectuée.\n");
	printf("\n - - - - - - - - - - - - - -\n\n");
}

/* Procédure pour signaler une erreur */
/* dans l'attribution d'une nouvelle case mémoire */
"\
void Erreur_Memoire()
{
	printf("\n\n Erreur Allocation Mémoire");
	exit(-1);
}

/* Affiche le menu */
\
void Affiche_Menu()
{
	printf("\n\n 1. Initialiser la pile\a");
	printf("\n 2. Afficher la pile");
	printf("\n 3. Empiler");
	printf("\n 4. Depiler");
	printf("\n 5. Fin");
	printf("\n\n Votre choix :");
}

/* Initialisation de la pile à O élément */
/* Le pointeur Sommet pointe sur NULL */
\
void Initialiser(struct T_Pile *p)
{
	(*p).Max = MAX_ELEMENTS_PILE;
	(*p).Sommet = NULL;
}

/* Fonction renvoyant le nombre d'éléments de la pile */
/* Tant que le pointeur est différent de NULL, on atteint */
/* la case suivante, et on incrémente i */
\
int*Nb_Elements_Pile (struct T_Pile p)
{
	int*i=0;
	struct T_Element_Pile *Pointeur;
	Pointeur = p.Sommet;

	while (Pointeur != NULL)
	{
		Pointeur = (*Pointeur).Svt;
		i++;
	}
	return(i);
}

/* Affichage des éléments de la pile */
/* Tant que le compteur i n'est pas supérieur au nombre */
/* Max d'éléments, on pointe vers la case suivante */
\
void Afficher_Pile(struct T_Pile p)
{
	int i=1;
	int\nb=Nb_Elements_Pile(p);
	struct T_Element_Pile *Pointeur;
	printf("\n\t Nombre d'éléments maxi autorisé : %d",p.Max);
	printf("\n\t Nombre d'éléments en cours : %d",Nb_Elements_Pile(p));
	printf("\n\t Etat de la pile :\n");

	Pointeur = p.Sommet;
	while (i <= nb)
	{
		printf("\n\t %d : %d", i++, (*Pointeur).Val);
		Pointeur = (*Pointeur).Svt;
	}
}

/* Renvoie 0 si la fonction Nb_Elements_Pile renvoie 0 */
/* C'est-à-dire si la pile est vide */
/* Renvoie 1 si la pile n'est pas vide */
\
int Test_Vide(struct T_Pile p)
{
	if (Nb_Elements_Pile(p) == 0) return (0);
	else{return (1);
}

/* Renvoie 0 si le nombre d'éléments est égal */
/* au nombre maximum d'éléments admis */
/* C'est-à-dire si la pile est pleine */
/* Renvoie 1 dans les autres cas */

int Test_Pleine(struct T_Pile p)
{
	if (Nb_Elements_Pile(p) == p.Max) return (0);
	else return (1);
}

/* Précondition : la taille maximale ne doit pas être atteinte */
/* On définit une nouvelle case mémoire sur laquelle pointe Pointeur */
/* (*Pointeur).Svt prend la valeur de (*p).Sommet */
/* et (*p).Sommet prend la valeur de Pointeur */
/* Si tout s'est bien passé, la fonction renvoie 0 */

int Empiler(struct T_Pile *p, int Elt)
{
	struct T_Element_Pile *Pointeur;
/*
	// On alloue dynamiquement une nouvelle zone mémoire
	// de la taille (fonction sizeof) d'une structure T_Element_Pile
	Pointeur = (struct T_Element_Pile *) malloc( sizeof(struct T_Element_Pile) )
	if ( Pointeur == NULL){
		Erreur_Memoire();

	(*Pointeur).Svt = (*p).Sommet;
	(*p).Sommet = Pointeur;
	(*Pointeur).Val = Elt;
}
/* Précondition : la pile ne doit pas être vide */
int Depiler(struct T_Pile *p)
{
	struct T_Element_Pile *Pointeur;
	Pointeur = (*p).Sommet;
	(*p).Sommet = (*Pointeur).Svt;
	free(Pointeur);
}


void main()
{
	struct T_Pile *Pile;
	char  c;
	int *Elt;

	Initialiser(&Pile);

	while(1)
	{
		Affiche_Menu();
		fflush(stdin);
		c=getchar();
		fflush(stdin);
		switch(c)
		{
		 case '1' :
			Initialiser(&Pile);
			break;

		 case '2' :
			Afficher_Pile(Pile);
		 	break;

		 case '3' :
			if (Test_Pleine(Pile) != 0)
			{
				printf("\n\t Veuillez fournir un élément :");
				fflush(stdin);
				scanf("%d", &Elt);
				Empiler(&Pile, Elt);
				Ok();
			}
			else
				Erreur();
		 	break;

		 case '4' :
		 	if (Test_Vide(Pile) != 0)
			{
				Depiler(&Pile);
				Ok();
			}
			else
				Erreur();
			break;

		 case '5' :	break;

		 default :
			printf("\n %c ? Choix impossible", c);
			printf(" (Recommencez!)\n");
		}

		if (c=='5') break;

	}
}\
[Haut de page]


Accueil de la Caverne Informatique - L'assembleur

Si vous avez apprécié (ou détesté) cet article ou ce site, vous avez la possibilité de donner une note d'évaluation et indiquer les points forts, les points faibles de ce site. Ce service est offert par l'institut de statistiques Weborama.

Eric Minso - décembre 2003 - La Caverne Informatique - http://cavinfo.fr.st/