Avatar

simbilou

Aucun contenu publié
Aucun abonné

La mort rend les humains précieux et pathétiques, ils émeuvent par leur condition de fantôme; chaque acte qu’ils accomplissent peut être le dernier, aucun visage qui ne soit à l’instant de se dissiper comme un visage de songe. Tout, chez les mortels, à la valeur de l’irrécupérable et de l’aléatoire.

Borges

Code minimal

Lorsque sur le forum, vous venez avec un code qui pose problème, vous pouvez être tenté de copier-coller des bouts votre code, ou encore pire, la totalité de celui-ci. Pourquoi est-ce une mauvaise idée ? Parce que :

  • Votre code contient beaucoup de choses qui n’ont aucun rapport avec le problème
  • Les gens abandonnent la lecture devant autant de code
  • On ne peut pas tester le code nous même
  • Tout le monde perd du temps parce que le problème est perdu dans le foulli du code

Ce qu’il faut faire est de présenter un code minimal. C’est à dire d’extraire les lignes de code concernées de votre programme et d’écrire un nouveau programme les utilisants. Peut importe que le nouveau code ne fasse pas exactement la même chose que le programme original, l’important est que cela reproduise l’erreur.

Il faut absolument que :

  • Le code soit complet, que l’on puisse le tester
  • Le code soit le plus simple possible. Idéalement un seul main(), on ne veut pas s’embêter à mettre en place un projet complexe pour tester votre code
  • Le code reproduise le problème initial
  • Le programme ne dépende pas de ressources externes (sauf si ça concerne le problème)

En réduisant ainsi votre code, il y a de bonne chance pour que vous trouviez tout seul la solution à votre problème : les choses sont plus claires et plus évidente avec peu de code. Et si vous ne trouvez pas, quelqu’un vous donnera sans doute une réponse très rapidement puisque la quantité de code à lire est petite.

traduction et adaptation de ce post


Programmation modulaire en C

Compiler avec plusieurs fichiers

C’est un sujet qui pose pas mal de problèmes aux débutants, et même parfois aux moins débutants parce qu’il n’est pas si bateau qu’il n’y parait. Pour être parfaitement à l’aise avec la programmation modulaire, il faut avoir bien compris les deux étapes de la création d’un programme : la compilation et l’édition de lien.

Je vais supposer ici que vous avez lu la partie du tutoriel de M@teo21 sur la programmation modulaire ou que vous êtes tout du moins un peu familiarisé avec l’inclusion de headers. Normalement à ce stade vous savez à peu près faire mais c’est encore flou dans votre tête et parfois vous avez des erreurs bizarres du style undefined reference to function ’foo’ in bar.o que vous mettez beaucoup de temps à résoudre.

NB : je vous invite à lire la bio de nepser pour savoir comment remédier a cette erreur de manière pratique. Ici vous comprendrez un peu mieux de quoi il s’agit.

Déclaration, Définition

J’aimerai exposer ici brièvement deux mots de vocabulaires qui nous seront utile par la suite pour comprendre le rôle des headers. En C, on fait la distinction entre la déclaration d’un symbole (i.e. une variable ou une fonction) et sa définition.

Déclarer un symbole, c’est juste signaler au compilateur que ce nom (associé, à un type pour une variable, un prototype pour une fonction) existe.

Définir un symbole, c’est allouer un espace mémoire effectif pour stocker cette variable ou fonction.

Les variables sont, la plupart du temps, déclarées et définies en même temps. Les lignes suivantes sont des exemples de déclarations/définitions.

/* Les variables sont habituellement déclarés et définis en une fois */
int truc; // déclaration et définition
char lettre = 'F'; // déclaration, définition et initialisation
float tab[6]; // déclaration et définition

Pour les fonctions vous connaissez déjà. On déclare une fonction en donnant son prototype et on la définit en écrivant son corps.

void Affiche(void); // déclaration

void Affiche(void) // déclaration et définition
{
    printf("test");
}

Notez que l’on peut déclarer autant de fois que l’on souhaite un symbole, à condition d’être cohérent sur son (proto)type. En revanche la définition doit être unique dans le programme, sans quoi l’éditeur de lien nous informera d’une définition multiple (on y reviendra).

Implicit declaration, undefined reference

Ce sont des messages qui reviennent très souvent et nous allons les décrypter ici. Afin de se donner des outils pour travailler nous allons créer le « Hello world » suivant.

#include <stdio.h>

void HelloWorld (int nb)
{
    printf("Hello, world %d !", nb);
}

int main (void)
{
    HelloWorld (42);
    return 0;
}

Ce qui affiche naturellement :

Hello, world 42 !

Si vous êtes perdus à ce stade retournez apprendre les base du C.
Plaçons maintenant la fonction HelloWorld dans un fichier séparé hello.c, on a alors.

/* fichier main.c */

int main (void)
{
    HelloWorld (42);
    return 0;
}

/* fichier hello.c */

#include <stdio.h>

void HelloWorld (int nb)
{
    printf("Hello, world %d !", nb);
}

Lancez la compilation/édition de lien. Vous vous en doutez, cela ne devrait pas marcher. En fait, deux cas de figures sont possible :

  • Si le fichier hello.c a bien été ajouté a votre projet, vous aurez un warning Warning : implicit declaration of function 'HelloWorld' in main.c. C’est seulement un warning, le programme est bien créé et s’exécute normalement (c’est exceptionnel, en temps normal un warning c’est mauvais signe !)

  • Sinon vous aurez , en plus du warning ci-dessus, un affreux Error : undefined reference to function 'HelloWorld' in main.o

Réfléchissons à ce qui viens de se passer en commençant par le warning. En fait c’est plutôt simple, le compilateur en travaillant sur le fichier main.c voit un appel à la fonction 'HelloWorld' dont il n’a jamais entendu parler. Il se retrouve un peu bloqué, il doit placer dans le code machine généré un appel à une fonction dont il ignore l’existence. Il pourrait s’arrêter ici tout net en refusant d’aller plus loin. Mais décidément le compilateur est bonne pomme, alors il va faire un acte de fois et déclarer la fonction de lui même pour pouvoir continuer. Cependant comme ce genre d’initiative peut être mauvaise il vous prévient de cette déclaration implicite avec le fameux warning implicit declaration. Le compilateur pari sur le fait que vous maitrisez la situation et suppose que la fonction 'HelloWorld' existe bien quelque part.

Il est important de remarquer ici que ce warning se fait à la compilation au sens propre du terme. C’est vraiment le compilateur (et non l’éditeur de lien) qui vous tape sur les doigts.

Un dernier point avant de passer au deuxième message. On est en droit de se demander a quoi sert-t-il de déclarer nous même les fonctions si le compilateur sais le faire lui même. La raison est toute simple : il ne connait pas le prototype des fonctions déclarées implicitement. Il va donc essayer de le deviner ce qui peut poser problème quand il se trompe. Et de toute façon cela nous prive de beaucoup de vérification à la compilation, et croyez moi ce genre de garde-fou ce n’est pas du luxe !

L’étape de compilation étant finis, l’éditeur de lien va récupérer toute les fonctions du projet maintenant traduites en langage machine pour faire l’exécutable. Mais que se passe-t-il si une des fonctions venaient à manquer ? Et bien l’éditeur de lien va vous faire part du cuisant échec en décrivant la/les fonctions incriminée(s).
Dans notre cas, undefined reference to function 'HelloWorld' in main.o. Remarquons que l’erreur est dans main.o et non main.c puisque c’est l’éditeur de lien qui vous envoi l’erreur et que ce dernier travail avec les fichiers objets (.o).

Déclarons nos symboles !

Ici on suppose que le fichier hello.c est bien ajouté au projet. La question est, comment faire disparaitre notre ennuyeux warning ? Je suis sur que vous l’aviez deviné, il va falloir déclarer la fonction ’HelloWorld’. Pour cela rien de plus simple, il suffit de placer son prototype au début du fichier main.c .

Ceci est peut être perturbant au début, après tout la fonction 'Helloworld' n’est pas définit dans le fichier main.c. Mais essayer, vous verrez bien que ça fonctionne.

En fait pour être tout à fait propre, il faudrait rajouter le mot clef extern devant notre prototype. Ce mot clef permet de déclarer un symbole sans le définir, mais il est optionnel ici car on a vu que donner le prototype d’une fonction ne faisait que de la déclarer.

Et c’est tout, maintenant ça compile, et ça linke (de l’anglais linker, éditeur de lien) sans problème.

Toutefois, l’utilisation du mot-clef extern s’avère indispensable pour déclarer une même variable global dans plusieurs fichiers. Supposons que, malgré toutes les mises en gardes et menaces d’apocalypses programmationnels vous voulez utilisez une variable globale. Modifions notre exemple pour illustrer la situation.

/* fichier main.c */

extern void HelloWorld (void);
int global_int = 42;

int main (void)
{
    HelloWorld ();
    printf("%d, nice number.", global_int);
    return 0;
}

/* fichier hello.c */

#include <stdio.h>

void HelloWorld (void)
{
    printf("Hello, world %d !", global_int);
}

Évidement, ça plante. Le compilateur n’apprécie pas beaucoup qu’on utilise global_int sans l’avoir déclaré et nous le signal à sa façon.

hello.c: In function ’HelloWorld’:
hello.c:5:32 error : ’global_int’ undeclared (first use in this function)

(Déclarer implicitement des variables il ne sait pas faire).

D’un autre coté si on essaye de placer int global_int; aussi en haut du fichier hello.c cela ne va pas non plus car on a vu que cela revenait à déclarer et définir la variable or on ne veux pas définir un autre variable, c’est la même que dans main.c ! Si on essaye quand même, on se retrouve donc avec error symbol 'global_int' multiply defined1. Pour se sortir de cette situation délicate il faudrait pouvoir déclarer la variable sans la définir, et c’est justement ce que extern permet de faire. Elle est pas belle la vie ?

Rajoutons donc extern int global_int; au début du fichier hello.c, et tout va pour le mieux dans le meilleur des mondes possible. :)

Et les headers dans tout ça ?

Maintenant que l’on a mis à plat un peu tout ce fouillis, on peut enfin comprendre pleinement le rôle du header. Dans un vrai projet, les fonctions d’un même fichier .c peuvent être utilisées à de nombreux endroits. Alors plutôt que d’écrire sans cesse au début des fichiers .c les même prototypes, on s’est dit que ce serait pas mal de réunir tout plein de déclarations de symboles dans un même fichier et d’utiliser le préprocesseur pour venir recopier ces déclarations en haut de nos fichiers sources en une ligne. Bingo, c’est justement ce que fait la directive #include.

Cela signifie que les headers sont fait uniquement pour la déclaration des fonctions (et éventuellement des variables globales), jamais pour leur définition. Donc mettez bien le mot clef extern devant les variables globales de vos fichiers .h sinon vous aurez des problèmes dès que vous inclurez votre header plus d’une fois.

Un petit mot sur les bibliothèques

Si vous avez bien compris ce qui a été dit plus haut, vous devriez comprendre ces histoires de linkage de bibliothèques. En effet, dans le header il n’y que des déclarations donc faire par exemple #include <SDL/SLD.h> ça permet de rassurer votre compilateur mais l’éditeur de lien il s’en fiche pas mal. Il lui faut des définitions, contenu dans les fichiers binaires de la SDL, ce sont les fameux .lib ou .a suivant votre OS. D’où l’importance d’expliciter les bibliothèque à linker.

Vous aurez peut être remarqué que lorsque l’on fait #include <stdio.h>, on ne linke rien du tout et pourtant, on peut utiliser printf(), scanf() et compagnie. C’est en fait que la bibliothèque standard est tellement indispensable qu’elle est linké par défaut. Votre éditeur de lien peut aussi linker d’autres bibliothèques par défaut d’ailleurs. Sous windows par exemple l’API windows (dont le header est windows.h) l’est généralement. Mais cela dépend de votre éditeur de lien.

Conclusion

Voila, j’espère que ces explications vous aurons été utiles, pour toute correction, remarque, suggestion, n’hésitez pas à me MP. Pour les plus curieux d’entre vous, ceux vraiment intéresser par ce qui passe derrière le rideau je recommande le tutoriel de Taurre sur les identificateurs en lecture complémentaire (attention ce n’est pas facile d’accès).


Aller plus loin en C

Vous avez fini le tuto officiel et vous en voulez plus ?

Le langage C est relativement petit c’est vrai, mais ne croyez surtout pas que vous en avez déjà fait le tour. Voici une petite sélection de tutoriels du SdZ particulièrement intéressant et qui affuterons vos compétences. L’apprentissage en profondeur de tout ce qui est présenté dans ces tutos prend plusieurs années, ne soyez pas trop pressé et allez doucement. J’ai classé les tutos par catégories pour que vous vous y retrouviez un peu.

Cette liste n’est en aucun cas exhaustive, ce sont juste des tutos que j’ai lu et trouvé bien fait à titre personnel. J’ai des raisons de penser qu’ils peuvent être utile à beaucoup de gens qui ont finis le tuto officiel, qui veulent continuer mais qui sont privé de boussole.

Notez qu’il existe d’autres tutos sur le C qui partent prétendument de zéros mais qui sont nettement plus rigoureux, complet… et dur à suivre. Je pense à celui-ci en particulier. J’imagine qu’il serait pénible après avoir finit le tuto officiel d’en commencer un autre depuis le début mais vous pouvez toujours jeter un coup d’œil pour voir.

Arrêtez de poster le moindre de vos problèmes sur le forum

Si après avoir cherché dans la FAQ et lu la doc vous ne trouvez pas, vous pouvez encore vous en sortir tout seul, voici comment.

Récupération de données sécurisée

Parce qu’on sait tous qu’il ne faut pas faire confiance à l’utilisateur.

Encore des notions du C a connaitre

Et oui, il y a encore des mécanismes fondamentaux du langage dont vous ignorez complètement l’existence. Rassurez-vous ce sont les derniers.

Génie logiciel et SDL

Je connais bien les bases du C mais j’ai essayé de faire des truc avec la SDL et j’ai juste un code monstrueux et une application a moitiée fonctionnelle.

zéro_en_galère

En effet, pas facile d’avoir un code un peu cohérent. Heureusement notre ami Fvirtman est la et ouvrez grand vos mirettes, c’est passionnant ! C’est aussi plus général que la SDL, ce sont des excellents exemples sur comment architecturer un peu son programme.

Algorithmie

C’est bien plus général que le C mais si la programmation vous passionne, un jour ou l’autre faudra y passer.

Sans IDE. A moi la console !

Parce que on peut vraiment pas frimer si on se sert de la GUI.

Pour la culture

Cela ne vous aidera pas forcément à mieux coder mais vous y trouverez peut être des réponses à vos questions existentielles sur le C.

La norme c’est ce qui contient la vérité ultime sur le langage C, c’est elle qui dicte ce qui est standard et ce qui ne l’est pas, et donc par voie de conséquence, ce qui est portable et ce qui ne l’est pas. C’est assez imbitable mais dans les cas limites et tordus du langage, c’est toujours bien de se rapporter à celle-ci.


  1. le message exacte peut varier suivant votre compilateur

La répétition est la base de l’enseignement.  — ☮ ♡

Derniers sujets créés Voir tout

Signaler ce profil