La majorité des gens ici (puisque pas mal développent encore sous Windows) qui développent des "librairies" C n'en développent en réalité pas, je pensais en faire un topic, et le voici.
Compilation
La première chose que vous devriez avoir appris sur les langages de programmation, c'est la distinction entre les langages
compilés et les langages
interprétés... mais je reviens dessus pour m'assurer que c'est bien clair.
Imaginez que vous avez un livre en japonais et que vous ne comprenez que le français. Pour le traduire, vous avez plusieurs possibilités :
- séparer l'étape de traduction (compilation) et l'étape de lecture (exécution), ainsi une fois traduit le livre peut être lu autant de fois qu'il le faut et rapidement, en oubliant qu'il fallait qu'il soit traduit ;
- traduire à la volée, ce qui implique que vous devez traduire le livre en français à chaque fois que vous le lisez (à l'aide d'un traducteur, un interpréteur).
Pour le code, c'est la même chose : l'ordinateur ne lit que du code machine (le français), et vous codez dans un langage autre (le japonais peut ici représenter le C, le C++, ...). Donc la compilation, c'est l'art de transformer du texte organisé en du code machine.
Fichiers objet
À savoir, tout ce que je vais dire à partir de là concerne le format
ELF. Ce format, que vous utilisez sans doute déjà sans le savoir, est un format qui peut se décliner en plusieurs types de fichiers : des fichiers objets (repositionnables), des exécutables, et d'autres choses hors-sujet.
Le format ELF, c'est plus ou moins des symboles (avec une table de correspondance symboles/adresse dans le fichier) et des références à des symboles externes (qui ne sont pas dans le fichier), du code machine et des données brutes, organisés dans des sections.
Prenons du code C comme exemple :
// Prototypes
int fonction2();
// Globales
int globale5 = 12346;
int fonction1(void)
{
// les variables non-statiques ne génèrent pas de symbole
int variable8 = 44;
return (fonction2() + globale5 + variable8);
}
Ici, le fichier objet (une fois que GCC sera passé) contiendra deux symboles :
_globale5 et
_fonction1, et une référence à un symbole :
_fonction2 (puisque celle-ci est utilisée dans fonction1). (oui, il faut savoir que le compilateur C ajoute un underscore devant tous les noms de fonctions/globales/variables statiques).
Une question qui peut vous venir à l'esprit, c'est "mais du coup, les globales et les fonctions donnent la même chose dans le fichier objet, comment on sait que ce sont des globales et des constantes ?". Cette question est hors de la portée de ce tutoriel, mais elle est intéressante, et pour répondre à celle-ci, vous devrez comprendre au moins un assembleur (du coup, si vous êtes là, j'imagine que ce sera davantage l'assembleur SuperH).
Link
Le linker, c'est le logiciel qui va prendre des fichiers objets et des libs statiques (on va en parler après) et qui va mélanger tout ça ensemble pour en faire un exécutable.
Si vous faites un exécutable avec simplement le fichier objet présent ci-dessus, il va vous produire une erreur du genre "Le symbole
_fonction2 requis n'a pas été trouvé", il faut donc lui fournir un autre fichier objet avec le symbole _fonction2 dedans.
Par ailleurs, une fois que l'exécutable est créé, on n'a plus besoin de la table des symboles ou de la table des sections. Mais sachez que le fichier en
.elf que
le tutoriel de Lephenixnoir vous fait générer comporte encore cette table des symboles et des sections, rien ne vous empêche d'aller voir pour vérifier avec
sh3eb-elf-nm.
Bibliothèques statiques
(oui, on dit une bibliothèque, puisqu'on emprunte des objets dans des bibliothèques (et non une librairie), ou une "lib", pour faire référence au terme anglais)
Une bibliothèque statique est tout bêtement une archive (un fichier avec plein de fichiers dedans, comme les fichiers ZIP ou TAR) de fichiers objet.
On pourrait dire "oui mais du coup, au lieu de linker la lib, autant prendre ses fichiers objet et linker avec, non ?", et non, parce que la grande particularité des libs statiques, c'est que les fichiers objets qu'elle contient sont
facultatifs.
Reprenons le link. Lors de l'opération de link, tout ce qui est dans les fichiers objets que vous lui passerez directement sera mis dans l'exécutable. Seulement, il va manquer pas mal de symboles, comme au hasard,
_memcpy ou
_ML_vram_address -- eh bien, pour trouver ces symboles manquants, le linker va parcourir tous les fichiers objet présents dans les libs statiques linkées et prendre ceux où se trouvent les symboles qu'il manque à l'exécutable.
On peut donc voir les libs statiques comme des collections de symboles, donc des collections de fonctions (et éventuellement de constantes et autres données brutes) qui ne seront peut-être pas toutes ajoutées à l'exécutable d'un projet les utilisant.
Comment créer une lib statique ?
Faire une lib statique, ce n'est pas sorcier, il suffit de :
- compiler les fichiers source de la lib en fichiers objet ;
- créer une archive avec tous les fichiers objet ;
- éventuellement, créer un index des fichiers objet présents dans l'archive et de leurs symboles pour faciliter le job du linker.
Attention cependant : n'oubliez pas que le linker va inclure non pas des fonctions, mais des fichiers objet. Si vous mettez toutes les fonctions de la lib dans un fichier source et que, du coup, vous ne faites qu'un seul fichier objet, ce fichier objet sera inclus dans l'exécutable finale dès que l'un des symboles présents dedans sera requis. Divisez bien vos fonctions en fichiers, l'idéal étant une fonction par fichier, mais il y a des cas particuliers : imaginons qu'une fonction soit appelée par une seule autre fonction, et qu'elle ne soit pas prévue pour être appelée par l'utilisateur, alors il faut la mettre dans le fichier source de cette autre fonction et même en faire une fonction statique (disponible uniquement depuis le fichier).
Par exemple, pour créer la
libmonochrome, son Makefile va faire :
sh3eb-elf-gcc -c -o obj/ML_vram_address.o src/ML_vram_address.c -I ./include -Wall -Wextra
sh3eb-elf-gcc -c -o obj/ML_clear_vram.o src/ML_clear_vram.c -I ./include -Wall -Wextra
...
sh3eb-elf-ar rc libmonochrome.a ./obj/ML_vram_address.o ./obj/ML_clear_vram.o ...
sh3eb-elf-ranlib libmonochrome.a
Gérer un projet de lib statique
Faire un simple projet pour créer la lib, c'est bien, mais penser à l'utilisateur, c'est mieux. Un projet de lib statique propose généralement trois étapes :
- la
configuration : on donne (ou laisse par défaut) les dossiers d'installation des fichiers header, des fichiers lib, ...
- la
création : créer la lib (avec les commandes données ci-dessus, par exemple) ;
- l'
installation : installer les fichiers prêts à l'emploi sur la machine.
Avec la
libmonochrome par exemple, pour créer la lib puis l'installer, on réalise trois commandes :
./configure --prefix=~/opt/sh3eb-elf
make
make install
Je vous conseille vraiment de garder ça aussi simple que ça pour l'utilisateur qui va se servir de votre lib derrière.
Libs déjà créées/portées
-
libfx (
1.0) : lib du SDK, originale par CASIO
-
libinput (
2.0,
topic dédié) : manipulation avancée du clavier, originale par Ninestars.
-
libmonochrome (
1.1,
topic dédié) : originale par PierrotLL, y a-t-il encore besoin de la présenter ?
-
librtc (
0.3,
topic dédié) : manipulation du temps à l'aide de la Real-Time Clock.
(peut-être qu'un jour, quelqu'un trouvera le moyen d'en faire des packages pour les principales distributions utilisées par les membres, à savoir Arch/Manjaro et des Debian-based ?)
Citer : Posté le 23/05/2016 13:42 | #
Du coup gint pourrait occuper moins de place, non?
Citer : Posté le 23/05/2016 13:45 | #
gint a un statut un peu particulier à cause de son interrupt handler (et du linker script qu'il impose).
M'enfin, j'ai cru comprendre que c'était réglé, donc à voir.
Mon blog ⋅ Mes autres projets
Citer : Posté le 23/05/2016 14:43 | #
Oui, si tu n'utilise pas toutes les fonctions de gint, tu n'aura pas à tout trimballer. Après il faut savoir que le gestionnaire d'interruption et les fonctions essentielles (getkey, dessin, etc.) sont celles qui prennent le plus de place.
Mais dans tout les cas, c'est développé de manière propre, faut pas s'en faire pour ça
Citer : Posté le 23/05/2016 19:50 | #
Étant donné que c'est léphé qui d'en occupe je ne m'inquiète pas pour ça, c'est juste que j'avais mal compris et que je pensai que quand il disait que ça prenais de la place qu'il voulait dire que ça compilait tout gint.
Citer : Posté le 23/05/2016 20:22 | #
Si je comprends bien, ça permet de séparer les fonctions et le compilo ne choisi que celle dont il a besoin et ne compile pas toute les fonctions ?
Citer : Posté le 23/05/2016 20:27 | #
En soit, GCC le fait déjà. Il n'ajoute à l'écécutable que les fonctions qui sont utilisées. Vous pouvez faire le test, là dessus il est bon. Par contre, c'est le compilo d'Hitachi qui est moins performant. Donc ça prend pas forcément moins de place, c'est juste que c'est en effet un chouille plus propre.
Citer : Posté le 23/05/2016 20:32 | #
Attention à la nuance : le linker va prendre les fichiers objets contenant les fonctions que le programme utilise. Une lib statique n'est pas fait pour être suffisante, elle permet juste de proposer des fonctions à l'utilisateur. (par exemple, la libc, contenant entre autres printf, malloc, etc)
(un fichier source donne exactement un fichier objet, donc s'il y a plusieurs fonctions dans un fichier source, et que lors du link, le linker se rend compte que le programme utilisateur reprend une des fonctions du fichier objet, il va prendre tout le fichier objet)
Mon blog ⋅ Mes autres projets
Citer : Posté le 23/05/2016 22:26 | #
Ce que je dis juste au dessus, c'est que si tu compile la bibliothèque depuis son .c (comme ce qui est fait actuellement), GCC n'ajoute au binaire que le code des fonctions qu'il utilise. Ce qui n'est pas le cas des .a compilés depuis un unique .c, on est d'accord.
Citer : Posté le 27/05/2016 21:21 | #
De toute façon gint est une lib statique, par essence. Si vous n'utilisez pas du tout un module, vous n'inclurez pas toute la lib. Mais en fait, vous devrez utiliser tous les modules. À moins que vous ne puissiez vous passer de dessiner à l'écran, d'afficher du texte ou d'utiliser le clavier.
L'interrupt handler n'est qu'un module comme un autre, excepté que vous ne pouvez pas linker sans. Le linker script définit un point d'entrée dont les dépendances incluent le gestionnaire.
Le tuto est pas mauvais, mais je pense qu'on peut faire un peu plus clair : peut-être en étant un peu plus factuel. Tu ne devrais pas insister sur le lien entre la répartition des fonctions dans les fichiers et la manière dont le compilateur lie les objets ?
Citer : Posté le 28/05/2016 02:01 | #
J'ai refait le tutoriel en détaillant un peu plus tout ce qui est ELF etc du coup.
Mon blog ⋅ Mes autres projets
Citer : Posté le 28/05/2016 14:01 | #
C'est plus clair et plus joli je trouve !
Pense à utiliser [ justify ] aussi
Citer : Posté le 18/12/2016 13:32 | #
Ça vaudrait peut-être le coup de passer ce sujet dans la liste des tutoriels utiles non ?
Ça m'évitera d'avoir à endurer le fait d'aller sur le profil de Cake à chaque fois pour le retrouver.Citer : Posté le 18/12/2016 22:07 | #
Fait