Aller au contenu

[Cours #6] Librairies (statiques) et fichiers Headers (.h)


AlexMog
 Share

Recommended Posts

Bonjour à vous,
Dans ce cours, nous allons voir à quoi corresponds un fichier header, ainsi que comment créer une  librairie, et enfin, les structures!

 

I- Qu'est-ce qu'une lib?
Une lib, aussi nommée librairie (dynamique ou statique) est un fichier binaire contenant une liste de fonctions prédéfinies.
Je vais vous donner un exemple de son utilisation, et pourquoi il est bon de créer une lib!
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.
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.
Pour mieux vous faire comprendre tout cela, créons trois fichiers distincts:
main.c:

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++));
}

Tentons à présent de compiler main.c:

cc main.c

 

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.
Maintenant, tentons de compiler tous nos fichiers en même temps:

cc main.c my_putchar.c my_putstr.c

 

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!

cc my_putchar.c
cc my_putstr.c

 

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?
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).
Créons donc nos .o!

cc -c my_putchar.c
cc -c my_putstr.c

 

(man cc pour plus d'informations)
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.
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.
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.
Nous l'appellerons libmy.a:
Pour créer notre lib, voici la commande linker (je vous laisse le soin de lire le man de celle-ci):

ar rc libmy.a my_putstr.o my_putchar.o

 

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.
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):

cc main.c -L./ -lmy

 

Hop! Nous avons notre a.out fonctionnel!
Vous savez à présent créer vos propres librairies!

II- Les fichiers Header
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.
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).
Un header, c'est donc un fichier de préparation à la compilation. Il contiendra ainsi les prototypes des fonctions utilisées dans notre programme.
Vous pouvez trouver des exemples de headers dans les librairies que nous avions déjà utilisé!
Souvenez-vous:
Nous avions utilisé:

#include <unistd.h>

qui corresponds à inclure le fichier .h de la libraire unistd. les <> signifient que le header se trouve dans le dossier include de notre compilateur.
Pour inclure un fichier local, il suffit de faire

#include "fichierlocal"

 

tout simplement!
Donc revenons sur notre histoire de warnings, nous allons créer un .h pour la lib précédente que nous avons créé:

#ifndef _MY_H_
#define _MY_H_
 void my_putchar(char);
 void my_putstr(char *);
#endif /* _MY_H_ */

Je vais vous expliquer le fichier lignes par lignes.
Mais avant cela, je vais vous expliquer ce qu'est un define.
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.
Voici un exemple simple:

#define VERSION "1.0.0"

J'ai définit VERSION comme ayant comme value 1.0.0
Je peux le ré-utiliser dans mon code, si j'inclus le fichier .h qui contient ce define, comme ceci:

int main(void)
{
  my_putstr(VERSION);
  return (0);
}

ATTENTION: il ne s'agit pas d'une variable. Un define est remplacé par le compilateur par la valeurqui lui est destinée!
J'espère que vous m'aurez compris...
Passons donc à la définition de notre fichier .h:

#ifndef _MY_H_

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.

#define _MY_H_

Si notre fichier n'a jamais été inclus, nous définissons qu'il l'a à présent été void my_putchar(char);

void my_putstr(char *);

On y ajoute ensuite les prototypes des fonctions utilisées...

#endif /* _MY_H_ */

Enfin, on ferme notre "#ifndef" (if not defined).
Nous y sommes, revoyons notre main à présent:

#include "my.h"

int main(void)
{
  my_putstr("coucou\n");
  return (0);
}

Compilons le:

cc main.c -lmy -L./

 

Et voilà! Plus aucun warnings ou erreurs! Nous pouvons passer à la suite!

 

III- Les Structures.
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).
Construction d'une structure:

struct s_nom_de_ma_struct
{
 int valeur_numerique;
 char valeur_caractere;
 typpage valeur_n'importe_quel_typpage;
};

C'est aussi simple que cela.
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
chaine de caractère "texte":

struct s_mastruct
{
  char *pseudo;
  char *texte;
};

Nous allons afficher les données de notre structure:

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);
}

Ce code nous affichera:

Pseudo: AlexMog
Texte: Coucou! :)

 

A SAVOIR: lorsque vous utilisez un pointeur sur structure (comme dans la fonction set_structure) les "." sont remplacés par "->".
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!
Rendez-vous dans le .h:

struct s_mastruct
{
  char *pseudo;
  char *texte;
};

et créons notre nouveau typage:

typedef struct s_mastruct
{
  char *pseudo;char *texte;
} t_mastruct;

Nous pouvons à présent déclarer nos structures comme ceci:

t_mastruct mastruct;

 

Cool n'est-ce pas ?
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)).

 

Voilà!
A très bientôt pour un prochain cours!
Cours écrit par AlexMog. Contact: alexmog [at] live [point] fr

  • Upvote 2
Lien vers le commentaire
Partager sur d’autres sites

Pourquoi ne pas utiliser 

#pragma once

a la place des include guards ?

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.".

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.

Modifié par AlexMog
Lien vers le commentaire
Partager sur d’autres sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Invité
Répondre à ce sujet…

×   Vous avez collé du contenu avec mise en forme.   Supprimer la mise en forme

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Chargement
 Share

×
×
  • Créer...