Aller au menu - Aller au contenu

Le préprocesseur


Informations sur le tutoriel

Avatar
Auteur : M@teo21
Difficulté : Facile
Visualisations : 26 399 845
Licence : Creative Commons BY-NC-SA


Plus d'informations Plus d'informations

Historique des mises à jour

  • Le 06/03/2010 à 01:19:01
    Ticket #1742 (mauvais prototype pour fputs)
  • Le 02/02/2010 à 12:11:56
    Ajoute le temps d'étude estimé
  • Le 01/02/2010 à 21:30:54
    Correction, ticket n°1558
Après ces derniers chapitres harassants sur les pointeurs, tableaux et chaînes de caractères, nous allons faire une pause.
Je veux dire par là que vous avez dû encaisser pas mal de chocs dans les chapitres précédents, et que je ne peux donc pas vous refuser de souffler un peu :)

Ceci étant, pas question de se reposer sans rien apprendre (ça va pas la tête ? ^^ ). On va donc voir ensemble un chapitre simple, contenant d'ailleurs quelques rappels. Ce chapitre va traiter du préprocesseur, ce programme qui s'exécute juste avant la compilation.
Ne vous y trompez pas : les informations contenues dans ce chapitre vous seront utiles. Elles sont juste faciles à comprendre... et ça nous arrange :p
Chapitre précédent Sommaire Chapitre suivant

Les includes

Comme je vous l'ai expliqué dans les tous premiers chapitres du cours, on trouve dans les codes sources des lignes un peu particulières appelées directives de préprocesseur.
Ces directives de préprocesseur ont la caractéristique suivante : elles commencent toujours par le symbole #. Elles sont donc faciles à reconnaître.

La première (et seule) directive que nous ayons vue pour l'instant est #include.
Cette directive permet d'inclure le contenu d'un fichier dans un autre, je vous l'ai dit plus tôt.
On s'en sert en particulier pour inclure des fichiers .h comme les fichiers .h des bibliothèques (stdlib.h, stdio.h, string.h, math.h...) et vos propres fichiers .h.

Pour inclure un fichier .h se trouvant dans le dossier où est installé votre IDE, vous devez utiliser les chevrons < > :

Code : C
1
#include <stdlib.h>


Pour inclure un fichier .h se trouvant dans le dossier de votre projet, vous devez utiliser les guillemets :

Code : C
1
#include "monfichier.h"


Concrètement, le préprocesseur est démarré avant la compilation. Il parcourt tous vos fichiers à la recherche de directives de préprocesseur, ces fameuses lignes qui commencent par un #
Lorsqu'il rencontre la directive #include, il met littéralement le contenu du fichier indiqué à l'endroit du #include.

Supposons que j'aie un "fichier.c" contenant le code de mes fonctions et un "fichier.h" contenant les prototypes des fonctions de fichier.c. On pourrait résumer la situation dans ce schéma tout simple :

Image utilisateur


Tout le contenu de fichier.h est mis à l'intérieur de fichier.c, à l'endroit où il y a la directive #include fichier.h

Imaginons qu'on ait dans le fichier.c :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include "fichier.h"

int maFonction(int truc, double bidule)
{
    /* Code de la fonction */
}

void autreFonction(int valeur)
{
    /* Code de la fonction */
}


Et dans le fichier.h :

Code : C
1
2
int maFonction(int truc, double bidule);
void autreFonction(int valeur);


Lorsque le préprocesseur passe par là, juste avant la compilation de fichier.c, il met fichier.h dans fichier.c. Au final, le code source de fichier.c juste avant la compilation ressemble à ça :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int maFonction(int truc, double bidule);
void autreFonction(int valeur);

int maFonction(int truc, double bidule)
{
    /* Code de la fonction */
}

void autreFonction(int valeur)
{
    /* Code de la fonction */
}


Le contenu du .h est venu se mettre à l'emplacement de la ligne #include :)

Ce n'est pas bien compliqué à comprendre, je pense d'ailleurs que bon nombre d'entre vous devaient se douter que ça fonctionnait comme ça.
Au moins là avec ces explications supplémentaires, j'espère avoir mis tout le monde d'accord ;) Le #include ne fait rien d'autre qu'insérer un fichier dans un autre, c'est important de bien le comprendre.

Si on a décidé de mettre les prototypes dans les .h, au lieu de tout mettre dans les .c, c'est principalement par principe. On pourrait à priori mettre les prototypes en haut des .c (d'ailleurs dans certains très petits programmes on le fait parfois), mais pour des questions d'organisation il est vivement conseillé de placer ses prototypes dans des .h.

Les defines

Nous allons découvrir maintenant une nouvelle directive de préprocesseur : le #define.

Cette directive permet de définir une constante de préprocesseur. Cela permet d'associer une valeur à un mot. Voici un exemple :

Code : C
1
#define NOMBRE_VIES_INITIALES 3


Vous mettez dans l'ordre :
  • le #define
  • le mot auquel la valeur va être associée
  • la valeur du mot


Attention, malgré les apparences (notamment le nom que l'on a l'habitude de mettre en majuscules) c'est très différent des constantes que nous avons étudiées jusqu'ici, telles que :

Code : C
1
const int NOMBRE_VIES_INITIALES = 3;


Les constantes occupaient de la place en mémoire. Même si la valeur ne changeait pas, votre nombre "3" était stocké quelque part dans la mémoire. Ce n'est pas le cas des constantes de préprocesseur :)

Comment ça fonctionne ? En fait, le #define remplace dans votre code source tous les mots par leur valeur correspondante. C'est comme la fonction "rechercher / remplacer" de Word si vous voulez ;)
Ainsi, la ligne :

Code : C
1
#define NOMBRE_VIES_INITIALES 3


... remplace dans le fichier chaque NOMBRE_VIES_INITIALES par 3.


Voici un exemple de fichier .c avant passage du préprocesseur :

Code : C
1
2
3
4
5
6
7
#define NOMBRE_VIES_INITIALES 3

int main(int argc, char *argv[])
{
    int vies = NOMBRE_VIES_INITIALES;

    /* Code ...*/


Après passage du préprocesseur :

Code : C
1
2
3
4
5
int main(int argc, char *argv[])
{
    int vies = 3;

    /* Code ...*/


Avant la compilation, tous les #define auront donc été remplacés par les valeurs correspondantes. Le compilateur "voit" le fichier après passage du préprocesseur, dans lequel tous les remplacements auront été effectués.

Quel intérêt par rapport à l'utilisation de constantes comme on l'a vu jusqu'ici ?


Eh bien, comme je vous l'ai dit ça ne prend pas de place en mémoire. C'est logique, vu que lors de la compilation il ne reste plus que des nombres dans le code source.

Un autre intérêt est que le remplacement se fait dans tout le fichier dans lequel se trouve le #define. Si vous aviez défini une constante en mémoire dans une fonction, celle-ci n'aurait été valable que dans la fonction puis aurait été supprimée. Le #define en revanche s'appliquera à toutes les fonctions du fichier, ce qui peut s'avérer vraiment pratique pour le programmeur.

Un exemple concret d'utilisation des #define ?
En voici un que vous ne tarderez pas à utiliser. Lorsque vous ouvrirez une fenêtre en C, vous aurez probablement envie de définir des constantes de préprocesseur pour indiquer les dimensions de la fenêtre :


Code : C
1
2
#define LARGEUR_FENETRE  800
#define HAUTEUR_FENETRE  600


L'avantage est que si plus tard vous décidez de changer la taille de la fenêtre (parce que ça vous semble trop petit), il vous suffira de modifier les #define puis de recompiler.

A noter : les #define sont généralement placés dans des .h, à côté des prototypes (vous pouvez d'ailleurs aller voir les .h des bibliothèques comme stdlib.h, vous verrez qu'il y a des #define !).
Les #define sont donc "faciles d'accès", vous pouvez changer les dimensions de la fenêtre en modifiant les #define plutôt que d'aller chercher au fond de vos fonctions l'endroit où vous ouvrez la fenêtre pour modifier les dimensions. C'est donc du temps de gagné pour le programmeur ;)

En résumé, les constantes de préprocesseur permettent de "configurer" votre programme avant sa compilation. C'est une sorte de mini-configuration en fait ;)


Un define pour la taille des tableaux



On utilise souvent les defines pour définir la taille des tableaux. On fait par exemple :

Code : C
1
2
3
4
5
6
#define TAILLE_MAX      1000

int main(int argc, char *argv[])
{
    char chaine1[TAILLE_MAX], chaine2[TAILLE_MAX];
    // ...


Mais... Je croyais qu'on ne pouvait pas mettre de variable entre les crochets lors d'une définition de tableau ?


Oui, mais TAILLE_MAX n'est PAS une variable ;)
En effet je vous l'ai dit, le préprocesseur transforme le fichier avant compilation en :

Code : C
1
2
3
4
int main(int argc, char *argv[])
{
    char chaine1[1000], chaine2[1000];
    // ...


... et cela est valide :)

En définissant TAILLE_MAX ainsi, vous pouvez vous en servir pour créer des tableaux d'une certaine taille. Si cela s'avère insuffisant par le futur, vous n'aurez qu'à modifier la ligne du #define, recompiler, et vos tableaux de char prendront tous la nouvelle taille que vous aurez indiquée :)


Calculs dans les defines



Il est possible de faire quelques petits calculs dans les defines.
Par exemple, ce code crée une constante LARGEUR_FENETRE, une autre HAUTEUR_FENETRE, puis une troisième NOMBRE_PIXELS qui contiendra le nombre de pixels affichés à l'intérieur de la fenêtre (le calcul est simple : largeur * hauteur) :

Code : C
1
2
3
#define LARGEUR_FENETRE  800
#define HAUTEUR_FENETRE  600
#define NOMBRE_PIXELS    (LARGEUR_FENETRE * HAUTEUR_FENETRE)


La valeur de NOMBRE_PIXELS est remplacée avant la compilation par (LARGEUR_FENETRE * HAUTEUR_FENETRE), c'est-à-dire par (800 * 600), ce qui fait 480000 ;)
Mettez toujours votre calcul entre parenthèses comme je l'ai fait.

Vous pouvez faire toutes les opérations de base que vous connaissez : addition (+), soustraction (-), multiplication (*), division (/) et modulo (%)


Les constantes prédéfinies



En plus des constantes que vous pouvez définir vous-mêmes, il existe quelques constantes prédéfinies par le préprocesseur. A l'heure où j'écris ces lignes, je dois vous dire que je ne les ai encore jamais utilisées, mais il n'est pas impossible que vous leur en trouviez une utilité donc je vais vous les présenter ;)

Chacune de ces constantes commence et se termine par 2 symboles underscore _ (que vous trouverez sous le chiffre 8, tout du moins si vous avez un clavier AZERTY).

  • __LINE__ : donne le numéro de la ligne actuelle
  • __FILE__ : donne le nom du fichier actuel
  • __DATE__ : donne la date de la compilation
  • __TIME__ : donne l'heure de la compilation


Je pense que ces constantes peuvent être utiles pour gérer des erreurs, en faisant par exemple ceci :

Code : C
1
2
printf("Erreur a la ligne %d du fichier %s\n", __LINE__, __FILE__);
printf("Ce fichier a ete compile le %s a %s\n", __DATE__, __TIME__);


Code : Console
Erreur a la ligne 9 du fichier main.c

Ce fichier a ete compile le Jan 13 2006 a 19:21:10



Les définitions simples



Il est aussi possible de faire tout simplement :

Code : C
1
#define CONSTANTE


... sans préciser de valeur.
Cela veut dire pour le préprocesseur que le mot CONSTANTE est défini, tout simplement. Il n'a pas de valeur, mais il "existe".

J'en vois vraiment pas l'intérêt !?


L'intérêt est moins évident que tout à l'heure, mais il y en a un et nous allons le découvrir tout à l'heure :)

Les macros

Nous avons vu qu'avec le #define on pouvait demander au préprocesseur de remplacer un mot par une valeur. Par exemple :

Code : C
1
#define NOMBRE 9


... signifie que tous les "NOMBRE" de votre code seront remplacés par 9. Nous avons vu qu'il s'agissait en fait d'un simple rechercher / remplacer fait par le préprocesseur avant la compilation.

J'ai du nouveau ! :)
En fait, le #define est encore plus puissant que ça. Il permet de remplacer aussi par... du code source tout entier ! Quand on utilise #define pour rechercher / remplacer un mot par un code source, on dit qu'on crée une macro.


Macro sans paramètres



Voici un exemple de macro très simple :

Code : C
1
#define COUCOU() printf("Coucou");


Ce qui change ici, c'est les parenthèses qu'on a ajoutées après le mot-clé (ici COUCOU()). Nous verrons à quoi elles peuvent servir tout à l'heure.

Testons la macro dans un code source :

Code : C
1
2
3
4
5
6
7
8
#define COUCOU() printf("Coucou");

int main(int argc, char *argv[])
{
    COUCOU()

    return 0;
}


Code : Console
Coucou


Je vous l'accorde, ce n'est pas original pour le moment ;)

Ce qu'il faut bien comprendre déjà, c'est que les macros ne sont en fait que des bouts de code qui sont directement remplacés dans votre code source juste avant la compilation.
Le code qu'on vient de voir ressemblera en fait à ça lors de la compilation :

Code : C
1
2
3
4
5
6
int main(int argc, char *argv[])
{
    printf("Coucou");

    return 0;
}


Si vous avez compris ça, vous avez compris le principe de base des macros déjà :)


Mais... On ne peut mettre qu'une seule ligne de code par macro ?


Non, heureusement il est possible de mettre plusieurs lignes de code à la fois. Il suffit de mettre un \ avant chaque nouvelle ligne, comme ceci :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#define RACONTER_SA_VIE()   printf("Coucou, je m'appelle Brice\n"); \
                            printf("J'habite a Nice\n"); \
                            printf("J'aime la glisse\n");

int main(int argc, char *argv[])
{
    RACONTER_SA_VIE()

    return 0;
}


Code : Console
Coucou, je m'appelle Brice

J'habite a Nice

J'aime la glisse



Remarquez dans le main que l'appel de la macro ne prend pas de point-virgule à la fin. En effet, c'est une ligne pour le préprocesseur, elle ne nécessite donc pas d'être terminée par un point-virgule


Macro avec paramètres



Pour le moment, on a vu comment faire une macro sans paramètre, c'est-à-dire avec rien entre les parenthèses. Le principal intérêt de ce type de macros, c'est de pouvoir "raccourcir" un code un peu long, surtout s'il est amené à être répété de nombreuses fois dans votre code source.

Cependant, les macros deviennent réellement intéressantes lorsqu'on leur met des paramètres. Cela marche quasiment comme avec les fonctions.

Code : C
1
2
3
4
5
6
7
8
9
#define MAJEUR(age) if (age >= 18) \
                    printf("Vous etes majeur\n");

int main(int argc, char *argv[])
{
    MAJEUR(22)

    return 0;
}


Code : Console
Vous etes majeur


Notez qu'on aurait aussi pu rajouter un else pour afficher "Vous êtes mineur". Essayez de le faire pour vous entraîner, ce n'est pas bien difficile. N'oubliez pas de mettre un antislash \ avant chaque nouvelle ligne


Le principe de notre macro est assez intuitif je trouve :

Code : C
1
2
#define MAJEUR(age) if (age >= 18) \
                    printf("Vous etes majeur\n");


On met entre parenthèses le nom d'une "variable" qu'on nomme age. Dans tout notre code de remplacement, age sera remplacé par le nombre qui est indiqué lors de l'appel à la macro (ici c'est 22).

Ainsi, notre code source de tout à l'heure ressemblera à ceci juste après le passage du préprocesseur :

Code : C
1
2
3
4
5
6
7
int main(int argc, char *argv[])
{
    if (22 >= 18)
    printf("Vous etes majeur\n");

    return 0;
}


Le code source a été mis à la place de l'appel de la macro, et la valeur de la "variable" age a été mise directement dans le code source de remplacement.

Il est possible aussi de créer une macro qui prend plusieurs paramètres :

Code : C
1
2
3
4
5
6
7
8
9
#define MAJEUR(age, nom) if (age >= 18) \
                    printf("Vous etes majeur %s\n", nom);

int main(int argc, char *argv[])
{
    MAJEUR(22, "Maxime")

    return 0;
}


Voilà en gros tout ce qu'on peut dire sur les macros. Il faut donc retenir que c'est un simple remplacement de code source qui a l'avantage de pouvoir prendre des paramètres.

Normalement vous ne devriez pas avoir besoin d'utiliser les macros très souvent. Toutefois, certaines bibliothèques assez complexes comme wxWidgets ou Qt (bibliothèques de création de fenêtres que nous étudierons bien plus tard) utilisent beaucoup de macros. Il est donc préférable de savoir comment cela fonctionne dès maintenant pour ne pas être bêtement perdu plus tard ;)

Les conditions

Tenez-vous bien : il est possible de réaliser des conditions en langage préprocesseur :D
Voici comment cela fonctionne :

Code : C
1
2
3
4
5
#if condition
    /* Code source à compiler si la condition est vraie */
#elif condition2
    /* Sinon` si la condition 2 est vraie` compiler ce code source */
#endif


Le mot-clé #if permet d'insérer une condition de préprocesseur. #elif signifie "else if" (sinon si).
La condition s'arrête lorsque vous insérez un #endif. Vous noterez qu'il n'y a pas d'accolades en préprocesseur.

L'intérêt, c'est qu'on peut ainsi faire des compilations conditionnelles.
En effet, si la condition est vraie, le code qui suit sera compilé. Sinon, il sera tout simplement supprimé du fichier pour le temps de la compilation. Il n'apparaîtra donc pas dans le programme final.


#ifdef, #ifndef



Nous allons voir maintenant l'intérêt de faire un #define d'une constante sans préciser de valeur, comme je vous l'ai montré tout à l'heure :

Code : C
1
#define CONSTANTE


En effet, il est possible d'utiliser #ifdef pour dire "Si la constante est définie".
#ifndef, lui, sert à dire "Si la constante n'est pas définie".

On peut alors imaginer ceci :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#define WINDOWS

#ifdef WINDOWS
    /* Code source pour Windows */
#endif

#ifdef LINUX
    /* Code source pour Linux */
#endif

#ifdef MAC
    /* Code source pour Mac */
#endif


C'est comme ça que font les programmes multi plateformes pour s'adapter à l'OS par exemple :)
Alors, bien entendu, il faut recompiler le programme pour chaque OS (ce n'est pas magique :D). Si vous êtes sous Windows, vous mettez un #define WINDOWS en haut, puis vous compilez.
Si vous voulez compiler votre programme pour Linux (avec la partie du code source spécifique à Linux), alors vous devrez modifier le define et mettre à la place : #define LINUX. Recompilez, et cette fois c'est la portion de code source pour Linux qui sera compilée, les autres parties étant ignorées.


#ifndef pour éviter les inclusions infinies



#ifndef est très utilisé dans les .h pour éviter les "inclusions infinies".

Comment ça une inclusion infinie ? ?


Imaginez, c'est très simple.
J'ai un fichier A.h et un fichier B.h. Le fichier A.h contient un include du fichier B.h. Le fichier B est donc inclus dans le fichier A.
Mais, et c'est là le hic, supposez que le fichier B.h contienne à son tour un include du fichier A.h ? Ca arrive quelques fois en programmation ! Le premier fichier a besoin du second pour fonctionner, et le second a besoin du premier.

Si on y réfléchit 10 petites secondes, on imagine vite ce qu'il va se passer :

  1. L'ordinateur lit A.h et voit qu'il faut inclure B.h
  2. Il lit B.h pour l'inclure, et là il voit qu'il faut inclure A.h
  3. Donc il inclut A.h dans B.h, mais dans A.h on lui indique qu'il doit inclure B.h !
  4. Rebelote, il va voir B.h et voit à nouveau qu'il faut inclure A.h
  5. etc etc.


Pas besoin d'être un pro pour comprendre que ça ne s'arrêtera jamais !
En fait, à force de faire trop d'inclusions, le préprocesseur s'arrêtera en disant "y'en a marre des inclusions !" et du coup bah votre compilation plantera :p

Comment diable faire pour éviter cet affreux cauchemar ?
Voici l'astuce. Désormais, je vous demande de faire comme ça dans TOUS vos fichiers .h sans exception :

Code : C
1
2
3
4
5
6
#ifndef DEF_NOMDUFICHIER // Si la constante n'a pas été définie` le fichier n'a jamais été inclus
#define DEF_NOMDUFICHIER // On définit la constante pour que la prochaine fois le fichier ne soit plus inclus

/* Contenu de votre fichier .h (autres includes` prototypes de vos fonctions` defines...) */

#endif



Vous mettrez en fait tout le contenu de votre fichier .h (à savoir vos autres includes, vos prototypes, vos defines...) entre le #ifndef et le #endif.
Comprenez-vous bien comment ce code fonctionne ? Je ne l'ai pas compris du premier coup quand on me l'a expliqué moi pour être tout à fait franc :D

Imaginez que le fichier .h est inclus pour la première fois. Il lit la condition "Si la constante DEF_NOMDUFICHIER n'a pas été définie". Comme c'est la première fois que le fichier est lu, la constante n'est pas définie, donc le préprocesseur rentre à l'intérieur du if.

La première instruction qu'il rencontre est justement :
Code : C
1
#define DEF_NOMDUFICHIER


Maintenant, la constante est définie. La prochaine fois que le fichier sera inclus, la condition ne sera plus vraie, et donc le fichier ne risque plus d'être réinclus.

Bien entendu, vous appelez votre constante comme vous voulez. Moi je l'appelle DEF_NOMDUFICHIER par habitude, maintenant c'est chacun ses petites manies ;)
Ce qui compte en revanche, et j'espère que vous l'aviez bien compris, c'est de changer de nom de constante à chaque fichier .h différent. Il ne faut pas que ça soit la même constante pour tous les fichiers .h, sinon seul le premier fichier .h serait lu et pas les autres ^^
Vous remplacerez donc NOMDUFICHIER par le nom de votre fichier .h.

Si vous voulez vérifier que je ne suis pas en train de vous raconter des bêtises, je vous invite à aller consulter les .h des bibliothèques standard sur votre disque dur. Vous verrez qu'ils sont TOUS construits sur le même principe (un ifndef au début et un endif à la fin). Ils s'assurent ainsi qu'il ne pourra pas y avoir d'inclusions infinies.

Q.C.M.

#include a pour rôle :
La constante prédéfinie __DATE__ retourne :
Quelle directive de préprocesseur indique la fin d'un #if ?
Je veux protéger mon fichier .h pour éviter qu'il soit inclus plusieurs fois. Pourtant avec le code suivant, je risque quand même une inclusion infinie. Pourquoi ?


Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#ifndef HEADER_MES_FONCTIONS

#include "autreFichier.h"

#define LARGEUR_PERSONNAGE      30
#define HAUTEUR_PERSONNAGE      60
#define VITESSE_PERSONNAGE      5
#define NOMBRE_VIES_BASE        6

void chargerPersonnage(int longueur, int largeur, int skin);
int etatPersonnage();
int niveauSuperieur(int niveauActuel);
double prixObjet(int objetAVerifier);

#endif

Statistiques de réponses au QCM


C'est marrant, j'ai presque l'impression de vous avoir enseigné un nouveau langage de programmation là ^^
Et c'est un peu vrai d'ailleurs, car le préprocesseur, ce fameux programme qui lit vos codes sources juste avant de les envoyer au compilateur, a son propre langage à lui.
On peut faire 2-3 autres petites choses dont je ne vous ai pas parlé ici, mais globalement on en a fait le tour. On utilise beaucoup les directives de préprocesseur dans les .h comme vous l'avez vu.


Ah, aussi une petite remarque qui ne mange pas de pain avant de terminer : je vous conseille vivement de mettre quelques retours à la ligne après le #endif à la fin de vos fichiers .h.
Evitez que #endif soit la dernière ligne du fichier, j'ai déjà eu des erreurs de compilation à cause de ça et j'ai eu du mal à comprendre au début d'où ça venait. J'avais l'erreur "No new ligne at the end of file bidule"

Mettez donc 2-3 retours à la ligne après le #endif comme ceci :

Code : C
1
2
3
#endif
 
 


Cette remarque vaut d'ailleurs aussi pour la fin des fichiers .c. Mettez toujours quelques retours à la ligne vides à la fin, ça ne coûte rien et ça évite des prises de tête inutiles.


Je n'ai jamais dit que la programmation était une science exacte hein ^^
Parfois, on tombe sur des erreurs tellement bizarres qu'on en vient à se demander s'il ne faudrait pas faire d'incantations vaudoues :lol:


Vous inquiétez pas si ça vous arrive, c'est l'métier qui rentre :D
Chapitre précédent Sommaire Chapitre suivant

Informations sur le tutoriel

Retour en haut Retour en haut

Créé : Le 29/07/2005 à 00:29:36
Modifié : Le 23/07/2009 à 18:52:18
Avancement : 100%

67 commentaires