Ainsi, pour faire afficher i sur 5 colonnes dans l'exemple ci-dessous, faut-il écrire :
int i;
... cout << "i = " << setw (5) << i; |
La fonction __libc_current_sigrtmin() donne la valeur du premier signal temps réel. Pour afficher les autres signaux, il faut faire varier l'indice dans l'intervalle semi-ouvert [1, __libc_current_sigrtmin()[.
Une mauvaise solution serait d'écrire :
for (int i = 1; i < __libc_current_sigrtmin(); ++i) { ... } |
En effet, le compilateur n'a aucune raison d'imaginer que cette fonction donne toujours le même résultat (au moins lors de l'exécution d'un processus donné), et donc qu'il s'agit en réalité d'une constante.
Il est extrêmement probable qu'il va évaluer la fonction à chaque boucle.
Il est donc préférable d'écrire :
const int CstSigRTMin = __libc_current_sigrtmin();
for (int i = 1; i < CstSigRTMin; ++i) { ... } |
Dans le même fichier <csignal>
existe une macro, définie par :
#define SIGRTMIN (__libc_current_sigrtmin ()) |
On pourrait donc être tenté d'écrire la boucle :
for (int i = 1; i < SIGRTMIN; ++i) { ... } |
Il faut se souvenir qu'une macro n'est pas obligatoirement une constante, malgré l'écriture qui pourrait le laisser penser, mais que le pré-processeur la remplace par son équivalent, à savoir l'appel de la fonction. Ce n'est donc pas une bonne solution. De façon générale, utiliser une macro en C++ est rarement une bonne idée (sauf pour raccourcir des chemins d'accès, mais ce n'est pas du C++, c'est destiné à la précompilation) !
La fonction psignal() récupère dans son paramètre une chaîne qu'elle affiche dans le flux cerr (en réalité, c'est du C, donc c'est dans le fichiers standard d'erreur stderr), avant d'y ajouter une autre information.
Il vous est demandé que cette chaîne soit la suivante :
"Signal xx signifie " |
dans laquelle xx est la représentation sous forme de chaîne de caractères d'une variable NumSig entière. Quelle que soit la solution envisagée, il faut transformer NumSig sous forme de chaîne, suivant une des solutions proposées dans le chapitre Conversions chaînes de caractères <--> Nombres.
Une solution est de transformer NumSig en une chaîne StrNumSig, de la compléter et de l'utiliser dans psignal() :
char Signal [9];
gcvt (NumSig, /* ndigit = */ 2, Signal); string StrNumSig ("Signal ");
psignal (NumSig, StrNumSig.c_str()); |
Une autre solution est d'injecter dans une chaîne flux les informations désirées :
const int CstTaille = 255;
char Signal [CstTaille]; ostrstream os (Signal, sizeof (Signal));
psignal (NumSig, os.str()); |
la dernière ligne pouvant être écrite (moins élégant)
:
psignal (NumSig, Signal); |
La définition du wrapper Sigaction() doit appeler la fonction système sigaction(), défnie dans <signal.h>, mais accessible dans std si le fichier inclus est bien <csignal>. Cette définition est faite dans le fichier nsSysteme.hxx, qui ne doit pas comporter la clause using namespace std. Elle nécessite donc le préfixage de l'appel de sigaction() par std::.
#undef LA_MACRO
#define LA_MACRO ... |
Rappelons qu'une macro n'a aucune raison d'être dans une espace de noms, et que, même si elle est physiquement dedans, sa "visibilité" est quand même globale comme le montre l'exemple ci-dessous :
// Fichier NameSpace
namespace nsNameSpace
} // nsNameSpace |
// Fichier main.cxx
#include <iostream> #include "NameSpace" int main (void)
|
Le remplacement de la ligne {1} par :
cout << "LA_MACRO = " << nsNameSpace::LA_MACRO << endl; |
provoque une erreur de syntaxe.
struct std::sigaction Action; |
le préfixage std:: étant inutile si l'espace de noms std est visible à cet endroit du programme.
Deux possibilités de codage sont possibles :
switch (argv [i][0])
{ case i : case I : ... break; case d :
|
ou bien
switch (toupper (argv [i][0]))
{ case I : ... break; case D : ... break;
|
Voir aussi Remarques 8 du TP sur les fichiers
Considérons par exemple le morceau de programme suivant, écrit en ADA :
procedure P1 is
VarInterne : Integer; procedure PInterne (ParamInterne : in Integer) is begin -- PInterne -- utilisation de VarInterne
begin -- P1 -- ...
end P1; |
Dans un fichier C++, les fonctions ne peuvent être imbriquées. Il faut donc les séparer. La communication de VarInterne entre les deux procédures (ce seront des fonctions en C/C++), peut être résolue de deux façons.
La première consiste à ajouter un paramètre à PInterne :
void PInterne (int ParamInterne, int & NewParam)
{ // utilisation de NewParam (donc indirectement, de VarInterne) } // PInterne() void P1 ()
PInterne (QuelqueChose, VarInterne); } // P1() |
Apparemment plus lourde que la précédente, cette solution peut être considérée comme meilleure par certains puristes de l'algorithmique, car le mélange de mode d'échange d'informations entre deux sous-programmes (passage par paramètres et variables globales) est considéré comme dangereux (error-prone). Cette solution est cependant totalement exclue lorsque le profil de la fonction PInterne() est imposé, par exemple par le système d'exploitation (cas des fonctions de traitement des signaux).
La seconde possibilité consiste à faire passer VarInterne en variable "globale" aux deux fonctions.
Cependant, la norme C++ préconise de limiter (interdire ...) l'utilisation d'identificateurs globaux, qui polluent l'espace des noms.
La solution complète du problème nécessite donc de "contenir" la visibilité de cette variable localement au fichier, au moyen de l'espace de noms anonyme, ce qui donne :
namespace
{ int VarInterne; void PInterne (int ParamInterne)
} // namespace anonyme void P1 ()
PInterne (QuelqueChose); } // P1() |
Seule la fonction P1() est visible de l'extérieur, et donc utilisable.
for ( ; !Fin; )
{ if (!Saisie && ! Fin) /* 1 */ pause (); if (Saisie)
|
Si un des deux signaux est reçu entre la sortie du test et l'entrée dans la fonction pause(), (instant marqué /* 1 */ ci-dessus), situation hautement improbable mais toujours possible, le processus ne sortira jamais plus de la pause. Ce n'est pas grave car l'utilisateur, voyant qu'il ne se passe rien, renverra probablement le signal (ou même plusieurs fois s'il s'excite rageusement !). C'est en revanche catastrophique si ce signal a été généré par un processus, qui peut alors se bloquer lui-même en attente d'une réponse. C'est ainsi que se créent les situations dites d'interblocage catastrophiques lorsqu'elles interviennent à l'intérieur de processus système.
Une autre erreur peut apparaître si SIGINT parvient au processus à l'instant indiqué /* 2 */ ci-dessus. En effet, le traitant met le booléen Saisie à vrai (il l'est déjà), indiquant par là qu'une demande de saisie a été effectuée, mais le booléen est immédiatement remis à faux au retour du traitant. Donc ce signal est perdu.
Il est pratiquement impossible de provoquer volontairement ces erreurs en testant, et cela laisse croire que le programme est juste (ce genre d'erreur est fréquent dans les devoirs). En revanche, si on temporise chacune des actions du programme en insérant des appels à la fonction sleep(), cela devient possible.
De façon générale, en programmation système, dissocier le test d'un indicateur d'une action associée laisse toujours la possibilité à un événement de s'insérer entre les deux. Il faut donc que les deux opérations soient atomiques, c'est-à-dire exécutées par le système sans interruption possible. Il faut donc choisir des outils offrant cette atomicité.
Pour conclure, il est maladroit et dangereux d'utiliser les signaux pour synchroniser des processus. Leur seul rôle est de signaler l'occurrence d'événements, dont le traitement ne doit pas être attendu, mais qui peuvent être pris en compte de façon synchrone (stratégie C) ou asynchrone (stratégie E).
Une solution partielle au problème est l'utilisation d'une attente (semi)-active :
for ( ; !Fin; )
{ for (!Saisie && ! Fin) sleep (Delai); if (Saisie)
|
Un Delai infini revient à la solution précédente. Un Delai raisonnable (quelques secondes) permet de consulter périodiquement les indicateurs. Cependant, cela diminue la réactivité de l'application, et devient intolérable dans un contexte "temps réel". Un Delai de quelques milli-secondes peut donner l'illusion de temps réel à l'échelle humaine (il faut alors utiliser nanosleep(), car le paramètre de sleep() est un entier exprimé en secondes). Plus le Delai est court, plus on se rapproche de l'attente active (Delai nul), qui consomme l'essentiel du temps CPU à consulter les variables.
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 01/08/2001 - Dernière mise à jour : 28/08/2001