|Home| <<Prev<< <-Back-- >>Next>>
|
Tous les documents de cette page sont sous licence Creative Commons BY-NC-SA . Merci de la respecter.
©A. Dragut |
Lorsque le code source d'un projet de programmation se trouve dans plusieurs fichiers, on doit pouvoir les compiler séparément. De même, si on en modifie un ou deux, on ne doit recompiler que ceux qui en dépendent. L'outil make permet de faire ceci de manière simple et flexible: on lui donne dans un fichier spécial la commande pour compiler chaque fichier source, et quand on doit le recompiler. Donc, avant de commencer avec la programmation système, nous apprendrons à nous servir de make, afin de mieux gérer un nombre plus grand de fichiers interdépendants.
Travail à faire
Travail à faire
// debut fichier file1.cxx, a mettre dans le repertoire dirfile
#include <iostream>
#include "notreEntete.h"
int main() {
int a,b;
std::cin >> a >> b;
int c (calculer(a,b));
std::cout << c;
return(0);
}
// fin fichier file1.cxx
// debut fichier file2.cxx, a mettre dans le repertoire dirfile
#include "notreEntete.h"
int calculer(int a, int b) {
return (a+b);
}
// fin fichier file2.cxx
// debut fichier notreEntete.h, a mettre dans le repertoire include int calculer(int a, int b); // fin fichier notreEntete.h
g++ -o file1.run file1.cxx file2.cxx -I../include
En regardant l'arbre de dépendances de l'exécutable file1.run on voit que
Heureusement nous disposons d'un outil astucieux -- make. On lui décrit ces règles de dépendances dans un fichier nommé makefile ou Makefile, et il s'en occupe. Le principe de l'exécution du make est d'évaluer d'abord la premiere règle rencontrée, ou celle dont le nom est specifié en argument de l'appel de make. L'évaluation d'une règle se fait recursivement. Si un fichier de dépendance est lui même un fichier cible d'une autre règle, cette règle est à son tour évalué.
Travail à faire
cible : dependDeFic1 dependDeFic2 dependDeFic3 <TAB> commande de compilation g++ -c (ou edition de liens g++ -o)
make
g++ -c -I../include file1.cxx g++ -o prog.run file1.o file2.osont exécutées par l'outil make, car seulement les cibles file1.o, file.r1.run avait besoin d'être à jour.
make cleansuivi d'un 'ls -l' pour constater ce qui s'est passé, et puis un autre 'make', suivi d'un autre 'ls -l', et une exécution de programme.
L'outil make comprend également la notion de variable pour rendre les choses plus génériques et donc plus flexibles.
De même, on peut imaginer que file2.cxx grandira pour offrir une bibliothèque de fonctions. Alors on peut l'archiver avec l'outil ar et le donner au compilateur/linker avec -lSys.
Travail à faire
COMPILER = g++ -c -I../include(cette ligne est toute seule, il n'y a pas une seconde ligne, avec TAB, etc.)
make cleanet
makepour constater que cela fonctionne comme avant.
make nom=file1pour obtenir file1.run. Il faut alors décrire les cibles et dépendances utilisant la variable nom. Pour les fichiers objets, on fait ainsi:
$(nom).o : $(nom).cxx ../include/notreEntete.h <TAB> $(COMPILER) $(nom).cxx
ar -cqs libSys.a file2.o; rm file2.o
g++ -s -o $(nom).run $(nom).o -L. -lSys
Le code source de projets téléchargés pour Unix/Linux comporte un outil de configuration s'adaptant au type de Unix et installation concrets, générant ensuite un makefile pour la compilation et édition de liens pour le projet.
Donc make est incontournable: maîtrisez rapidement ses fonctionnalités de base, et jetez un coup d'oeil sur sa page de man.

#include <iostream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main (int argc, char * argv []) {
if(argc < 2) {
cerr << "Usage: " << argv[0] << " <nomDeFicher>\n";
return(1);
}
struct stat statStruct;
stat(argv[1],&statStruct);
cout << argv[1] << " taille " << statStruct . st_size << "\n";
return(0);
}
g++ -o exempleStat.run exempleStat.cxx
./exempleStat.run exempleStat.cxx
./exempleStat.run blahEt si on lance le programme sans aucun argument?
./exempleStat.runPourquoi ?
| Un appel système ÉCHOUÉ positionne la variable globale errno (qui est déclarée dans errno.h en tant qu'extern, donc à l'édition de liens elle sera mise en correspondance avec la bonne variable du code système). De plus, la fonction de bibliothèque strerror() fournit une description lisible pour les humains associée à chaque valeur d'errno. |
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
using namespace std;
int main (int argc, char * argv []) {
if(argc < 2) {
cerr << "Usage: " << argv[0] << " <nomDeFicher>\n";
return(1);
}
struct stat statStruct;
const int valRet = stat(argv[1],&statStruct);
if ( -1==valRet) {
std :: cerr << " Erreur : " << argv[1] << " "
<< strerror (errno) << "\n" ;
return(errno);
}
cout << argv[1] << " taille " << statStruct . st_size << "\n";
return(0);
}
Faire cela après chaque appel
système est fastidieux, donc on verra de quelle manière nous nous
allégerons cette tâche pour tout ce cours et TD/TP.
./exempleStat.run blahUtilisez le man dans le shell, en lançant man 2 stat, pour trouver une explication du code d'erreur obtenu, plus détaillée que celle fournie par strerror(). Ecrivez-la.
Vous mettrez tous les fichiers créés au cours de chaque exercice des TP dans le répertoire dirfile de l'exercice courant, qui sera considéré comme répertoire courant. En passant d'un exercice à l'autre vous allez recopier le contenu de include et, souvent, y rajouter également des fonctions. Vous mettrez dans le répertoire
tar cvf - exo_01 | gzip -9 - > exo_01.tar.gz
Dans ces deux exercices on met d'abord le tout en place (premier exercice), et ensuite on utilise stat() pour obtenir des renseignements sur les fichiers.
Ne pas oublier de consulter les pages de man 2 nom_fonction, man 3 nom_fonction.
int main (int argc, char * argv []) {
// ...
try {
// le corps de l'exercice appellant des fonctions qui peuvent lever des exceptions
}
catch (const CExc & Exc) {
// on affiche les renseignements de l'erreur
}
....
}
class CExc: public std::exception
{
protected :
std::string m_info;
std::string m_nomf;
int m_descrfic;
bool m_qdescrfic;
protected :
std::ostream & _Edit (std::ostream & os) const;
public :
CExc (const std::string & NomFonction,
const std::string & Info) throw ();
CExc (const std::string & NomFonction,
int Descrfic) throw ();
virtual ~CExc (void)throw ();
friend std::ostream & operator << (std::ostream & os,
const CExc & Item);
};
nsSysteme::CExc::CExc (const std::string & NomFonction,
const std::string & Info) throw ()
: m_info (Info), m_nomf(NomFonction), m_descrfic(-1), m_qdescrfic(false) {}
...
Elle a deux constructeurs car tous les appels système n'ont pas toujours un nom de fichier disponible. On verra par la suite comment on s'en sert de l'autre.#include "nsSysteme.h"
#include <string>
#include <exception>
#include "string.h"
#include "CExc.h"
#include "nsSysteme.h" // wrappers système
// a verifier avec le man 2 ou man 3 de la fonction systeme les #include a rajouter
using namespace nsSysteme; // wrappers système
using namespace std;
int main (int argc, char * argv []) {
try {
// ecrivez le corps de l'exercice courant
}
catch (const CExc & Exc) {
cerr << Exc << endl;
return errno;
}
catch (const exception & Exc) {
cerr << "Exception : " << Exc.what () << endl;
return 1;
}
catch (...) {
cerr << "Exception inconnue recue dans la fonction main()" << endl;
return 1;
}
}// main()
make general
pour constater que le wrapper de stat() (l'espace de noms nsSysteme) est bien déclaré et défini. On finalise la mise en place, afin de pouvoir s'en servir tout du long des TP.Attention: ne compilez pas encore plus que ce qui est dit plus haut!
vous prenez contact avec un premier appel système (pour trouver la taille d'un fichier),
Datant d'avant l'apparition des classes, les structures permettent de rassembler des éléments de type différent au sein d'une entité repérée par un seul nom de variable. Conditions: deux champs ne peuvent avoir le même nom, les champs peuvent être de n'importe quel type hormis le type de la structure dans laquelle elles se trouvent. Une structure est composée d'éléments de taille fixe, et donc on peut créer des tableaux contenant des éléments de type structure.
La définition d'une structure:
struct Point {
int x;
int y;
char etiquette[7];
};
/*déclaration et utilisation: */
struct Point p = {3,5,"A(x,y)"};
p.x=4;
p.y=3;
...
/* et avec des pointeurs */
struct Point *q;
... /* allocation mémoire */
q->x = 3;
q->y = 5;
q->etiquette[0]= 'A';
q->etiquette[1]= '(';
...
stracepour avoir la trace des appels système effectués par votre programme. Si on est en bash, il faut faire par exemple
strace ./exo_02.run exo_02.run
Pour filtrer la sortie, on peut faire par exemple
strace ./exo_02.run exo_02.run 2>&1 | grep stat