[Cours #6] Librairies (statiques) et fichiers Headers (.h)
-
<p>Bonjour à vous,<br/>
Dans ce cours, nous allons voir à quoi corresponds un fichier header, ainsi que comment créer une librairie, et enfin, les structures!</p>
<p> </p>
<p><strong>I- Qu'est-ce qu'une lib?</strong><br/>
Une lib, aussi nommée librairie (dynamique ou statique) est un fichier binaire contenant une liste de fonctions prédéfinies.<br/>
Je vais vous donner un exemple de son utilisation, et pourquoi il est bon de créer une lib!<br/>
Imaginons que vous avez créé plusieurs fonctions, il vous serait pratique de les ré-utiliser dans un autre programme! Malheureusement, la compilation est très longue si il y a beaucoup de fonctions.<br/>
La lib est donc là pour deux choses: partager des fonctions sur diverses programmes, et éviter une compilation inutile sur des fonctions déjà près compilées.<br/>
Pour mieux vous faire comprendre tout cela, créons trois fichiers distincts:<br/>
main.c:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
my_putstr("coucou\n");
return (0);
}my_putchar.c:
void my_putchar(char c)
{
write(1, &c, 1);
}my_putstr.c:
void my_putstr(char *str)
{
while(str)
my_putchar((str++));
}</pre>
<p>
Tentons à présent de compiler main.c:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>cc main.c
<p> </p>
</div></blockquote>
<p>
Hum.. notre linker nous gueule dessus en nous disant qu'il est impossible de trouver la fonction "my_putstr". C'est normal! my_putstr n'a pas été compilée avec notre main. Elle ne fais donc pas partie du programme.<br/>
Maintenant, tentons de compiler tous nos fichiers en même temps:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>cc main.c my_putchar.c my_putstr.c
<p> </p>
</div></blockquote>
<p>Ah! Là ça marche! On a notre a.out qui est bien créé, et qui affiche bien "coucou".C'est un bon point, mais nous ne sommes pas avancés, j'ai envie de réutiliser my_putchar et my_putstr dans mes autres programmes, je vais tenter des les compiler pour voir si je peux les ré-utiliser sans avoir à les recompiler!</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>cc my_putchar.c<br/>
cc my_putstr.c
<p> </p>
</div></blockquote>
<p>
Hum... Mon linker me dit cette fois-ci qu'aucun main n'a été trouvé... Je ne peux donc pas compiler mes fonctions séparément?<br/>
Eh bien si! Il a une solution, les transformer en fichiers binaires non exécutables (aussi appelées lib), leur extentions est généralement .a pour un rassemblement de mini-libs, et .o pour les fichiers de fonctions de cette lib (pour résumer, les .o sont les briques, et .a est une partie du mur ! On utilise les briques pour construire cette partie).<br/>
Créons donc nos .o!</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>cc -c my_putchar.c<br/>
cc -c my_putstr.c
<p> </p>
</div></blockquote>
<p>
(man cc pour plus d'informations)<br/>
Voila! Je découvre my_putchar.o et my_putstr.o, tentez la commande "file my_putchar.o" pour voir qu'il s'agit bien d'un fichier binaire non exécutable.<br/>
C'est cool, mais pour l'instant mes fichiers sont séparés, j'aimerais bien tous les avoir dans un même "packetage" pour éviter de me trimballer 40 fichiers par programmes.<br/>
Eh bien, nous allons utiliser notre linker (et non plus notre compilateur) pour tous les liés dans un même package, un véritable fichier lib: un fichier .a.<br/>
Nous l'appellerons libmy.a:<br/>
Pour créer notre lib, voici la commande linker (je vous laisse le soin de lire le man de celle-ci):</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>ar rc libmy.a my_putstr.o my_putchar.o
<p> </p>
</div></blockquote>
<p>On peux en faite considérer les .o comme des briques, et les .a comme le ciment qui va maintenir ces briques sur le programme.<br/>
Nous avons enfin notre lib! Tentons à présent de compiler main.c avec notre lib (je vous laisse, encore une fois, le soin de lire le man de gcc):</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>cc main.c -L./ -lmy
<p> </p>
</div></blockquote>
<p>Hop! Nous avons notre a.out fonctionnel!<br/>
Vous savez à présent créer vos propres librairies!</p>
<p><strong>II- Les fichiers Header</strong><br/>
Nous avons vu précédemment comment créer notre propre librairie, mais néanmoins, nous avons, de temps à autres, des warnings qui apparaissent lors de notre compilation, voir même des erreurs car notre compilateur ne connaît pas les fonctions qu'il utilise.Les fichiers Headers ont plusieurs particularités. Il s'agit avant tout de fichiers d'"entête" chargés de fournir des informations au compilateur pour qu'il sache où chercher les fonctions, et leur utilité dans le programme.<br/>
Il permet aussi de rendre le code plus propre: nous pourrions faire nos headers directement dans notre .c, mais c'est plus moche, et la compilation d'un .h diffère de celle d'un .c (tentez cc *.h vous verrez bien).<br/>
Un header, c'est donc un fichier de préparation à la compilation. Il contiendra ainsi les prototypes des fonctions utilisées dans notre programme.<br/>
Vous pouvez trouver des exemples de headers dans les librairies que nous avions déjà utilisé!<br/>
Souvenez-vous:<br/>
Nous avions utilisé:</p>
<pre class="ipsCode prettyprint">
#include <unistd.h>
</pre>
<p>qui corresponds à inclure le fichier .h de la libraire unistd. les <> signifient que le header se trouve dans le dossier include de notre compilateur.<br/>
Pour inclure un fichier local, il suffit de faire</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>#include "fichierlocal"
<p> </p>
</div></blockquote>
<p>
tout simplement!<br/>
Donc revenons sur notre histoire de warnings, nous allons créer un .h pour la lib précédente que nous avons créé:</p>
<pre class="ipsCode prettyprint">
#ifndef MY_H
#define MY_H
void my_putchar(char);
void my_putstr(char );
#endif / MY_H */</pre>
<p>
Je vais vous expliquer le fichier lignes par lignes.<br/>
Mais avant cela, je vais vous expliquer ce qu'est un define.<br/>
Un define est une sorte de variable statique et constante. Elle permet de remplacer la valeur du define par la valeur associée à celui-ci.<br/>
Voici un exemple simple:</p>
<pre class="ipsCode prettyprint">
#define VERSION "1.0.0"</pre>
<p>
J'ai définit VERSION comme ayant comme value 1.0.0<br/>
Je peux le ré-utiliser dans mon code, si j'inclus le fichier .h qui contient ce define, comme ceci:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
my_putstr(VERSION);
return (0);
}</pre>
<p>
ATTENTION: il ne s'agit pas d'une variable. Un define est remplacé par le compilateur par la valeurqui lui est destinée!<br/>
J'espère que vous m'aurez compris...<br/>
Passons donc à la définition de notre fichier .h:</p>
<pre class="ipsCode prettyprint">
#ifndef MY_H</pre>
<p>
Notre première ligne de code permet d'éviter ce qu'on appelle une "double inclusion", en effet, si on inclue notre fichier .h dans plusieurs autres fichiers du même programme, nous pouvons créer de multiples inclusions, ce qui ne sert à rien, et ralentit la compilation.</p>
<pre class="ipsCode prettyprint">
#define MY_H</pre>
<p>
Si notre fichier n'a jamais été inclus, nous définissons qu'il l'a à présent été void my_putchar(char);</p>
<pre class="ipsCode prettyprint">
void my_putstr(char );</pre>
<p>
On y ajoute ensuite les prototypes des fonctions utilisées...</p>
<pre class="ipsCode prettyprint">
#endif / MY_H */</pre>
<p>
Enfin, on ferme notre "#ifndef" (if not defined).<br/>
Nous y sommes, revoyons notre main à présent:</p>
<pre class="ipsCode prettyprint">
#include "my.h"int main(void)
{
my_putstr("coucou\n");
return (0);
}</pre>
<p>
Compilons le:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>cc main.c -lmy -L./
<p> </p>
</div></blockquote>
<p>
Et voilà! Plus aucun warnings ou erreurs! Nous pouvons passer à la suite!</p>
<p> </p>
<p><strong>III- Les Structures.</strong><br/>
Une structure est un ensemble de données. Il permet de stocker plusieurs données sous un même typpage. (nous verrons en même temps la déclaration de typpages, grâce à typedef).<br/>
Construction d'une structure:</p>
<pre class="ipsCode prettyprint">
struct s_nom_de_ma_struct
{
int valeur_numerique;
char valeur_caractere;
typpage valeur_n'importe_quel_typpage;
};</pre>
<p>
C'est aussi simple que cela.<br/>
A savoir : Une structure doit être déclarée dans un .h!Nous allons créer une structure, qui contiendra une chaine de caractères "pseudo" et une autre<br/>
chaine de caractère "texte":</p>
<pre class="ipsCode prettyprint">
struct s_mastruct
{
char *pseudo;
char *texte;
};</pre>
<p>
Nous allons afficher les données de notre structure:</p>
<pre class="ipsCode prettyprint">
void set_structure(struct s_mastruct mastruct)
{
/ (*mastruct).pseudo = "AlexMog" est la même chose que mastruct->pseudo = "AlexMog" */
mastruct->pseudo = "AlexMog";
mastruct->texte = "Coucou! :)";
}int main(void)
{
struct s_mastruct mastruct;set_structure(&mastruct);
my_putstr("Pseudo: ");
my_putstr(mastruct.pseudo);
my_putchar('\n');
my_putstr("Texte: ");
my_putstr(mastruct.texte);
my_putchar('\n');
return (0);
}</pre>
<p>
Ce code nous affichera:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>Pseudo: AlexMog<br/>
Texte: Coucou!
<p> </p>
</div></blockquote>
<p>A SAVOIR: lorsque vous utilisez un pointeur sur structure (comme dans la fonction set_structure) les "." sont remplacés par "->".<br/>
Voilà, vous savez à présent vous servir des structures. Mais vous avez remarqué que taper "struct s_mastruct mastruct" est tout de même long pour la ré-utilisation de cette structure... Nous allons donc créer notre propre typage!<br/>
Rendez-vous dans le .h:</p>
<pre class="ipsCode prettyprint">
struct s_mastruct
{
char *pseudo;
char *texte;
};
</pre>
<p>et créons notre nouveau typage:</p>
<pre class="ipsCode prettyprint">
typedef struct s_mastruct
{
char *pseudo;char *texte;
} t_mastruct;</pre>
<p>
Nous pouvons à présent déclarer nos structures comme ceci:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>t_mastruct mastruct;
<p> </p>
</div></blockquote>
<p>
Cool n'est-ce pas ?<br/>
Vous remarquerez, pour ceux qui font de la programmation orientée objet, qu'une structure ressemble énormément à un objet, il s'agit en faite de la maman de l'objet connu actuellement! Nous verrons cela plus en détails, lorsque j'aborderais les notions sur la programmation en C Modulaire dans un prochain cours (dans longtemps donc, puisque nous devons voir les pointeurs sur fonctions avant (un objet = une structure contenant un ensemble de données et de pointeurs sur fonctions allouée dans la mémoire)).</p>
<p> </p>
<p>Voilà!<br/>
A très bientôt pour un prochain cours!<br/>
Cours écrit par AlexMog. Contact: alexmog [at] live [point] fr</p> -
<blockquote class="ipsQuote" data-cite="davydavek" data-ipsquote="" data-ipsquote-contentclass="forums_Topic" data-ipsquote-contentcommentid="7899" data-ipsquote-contentid="745" data-ipsquote-contenttype="forums" data-ipsquote-timestamp="1400792410" data-ipsquote-username="davydavek"><div>
<div>
<p>Pourquoi ne pas utiliser </p>
<pre class="ipsCode prettyprint">
#pragma once</pre>
<p>a la place des include guards ?</p>
</div>
</div></blockquote>
<p>Sortie du site de la doc C99: "The #ifndef/#define/#endif trick works on any C compiler, and on some of them it speeds up the compilation process. The #pragma trick is non-standards, and only works on few C compilers, and may result in different semantics in those that do not support it.".</p>
<p>Pour résumer, #pragma n'est pas standard. Et n'est pas définit dans la C99. Il n'est donc pas conseillé de l'utiliser.</p>
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login