Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Astuce pour compilation conditionnelle

20 réponses
Avatar
Michael Moreno
Bonjour,

Je recherche une astuce pour la compilation conditionnelle.

Supposons par exemple que j'ai de nombreuses lignes dans chaque classe
du type :

#if _DEBUG
...
#endif

Si je definis pour chaque classe un _DEBUG_MA_CLASSE alors je perds
l'aspect global du _DEBUG et c'est penible.
Si j'utilise pour chaque classe le meme _DEBUG alors c'est soit tout,
soit rien.

Aussi je me demande si la meilleure solution est de faire quelque chose
du genre dans chaque cpp

#define DEBUG_ON 1

et apres

#if _DEBUG == DEBUG_ON
...
#endif

D'apres vous est-ce la bonne solution a l'usage ou existe-t-il une
solution standard ?

Merci.

--
----
http://michael.moreno.free.fr/

10 réponses

1 2
Avatar
kanze
Olivier Azeau wrote:
wrote:
Olivier Azeau wrote:

[...]

PS : je ne sais pas si c'est off-topic tout ça (le
préprocesseur fait-il "partie du langage" ?)


Tout à fait. Section 16 de la norme, en ce qui concerne les
« directifs ». Quand on parle du préprocesseur, en fait, on
considère en général les six premières phase de traduction,
décrites en §2.1.

Ceci dit, ce n'est pas une raison d'en abuser. Dans la pratique,
c'est rarissime qu'on se sert de la compilation conditionnelle
dans le code bien écrit -- et jamais dans une fonction, comme il
en est question ici. (J'en ai quelques exemples qui trainent
dans le code à ma site, mais seulement dans les en-têtes, au
niveaux de portée du fichier.)

Voir par exemple
http://www.chris-lott.org/resources/cstyle/ifdefs.pdf.


Je suis d'accord pour proscrire la compilation conditionnelle en
dehors des en-têtes : sans aller jusqu'à une notion de code "bien
écrit", il s'agit surtout (pour moi) d'avoir un code lisible.


Je dirais que « code lisible » est une facteur essentielle du « code
bien écrit ».

Dans la pratique, j'utilise de moins en moins le compilation
conditionnelle, même dans les en-têtes. Parce que même là, une fois
qu'on commence à embriquer... Jette un coup d'oeil sur les en-têtes
de
Solaris, par exemple (qui doivent supporter quelque chose comme une
vingtaines de variants différents, selon les options spécifiées au
moyen
d'un -D).

Je trouve cependant acceptable de se limiter à la présence de
macros

paramètrées à l'intérieur des fonctions car je ne connais aucun
idiome

(hors utilisation du pre-processeur) qui permette d'inhiber la
génération de code binaire superflu (en tirant, par exemple, parti
des

optimisations du compilateur).


Je ne dis pas le contraire, bien que le motif, c'est en général moins
la
possibilité de les supprimer complètement que de pouvoir simplifier
l'écriture tout en ajoutant automatiquement des informations du genre
__LINE__ et __FILE__. D'après mon expérience, plus il faut tapper de
texte, moins les traces serviront. Je vise donc quelque chose du genre:

TRACE_FNC( nomDeFonction ) ;
TRACE_ARG( nom, valeur ) ;
TRACE_DATA << text ... ;
// ...

cf par exemple le code de l'article que tu mentionnes :
#ifdef NDEBUG
# define DEBUG(list) /* nothing */
#else
# define DEBUG(list) printf list
#endif
DEBUG(("oops: %s %dn", b, c));

qui devient en C++ :
#ifdef NDEBUG
# define DEBUG(list) /* nothing */
#else
# define DEBUG(list) someDebugOutputStream << list << std::endl
#endif
DEBUG("oops: " << b << c);


C'est effectivement un début, pour des programmes très simple.
Seulement@:

- J'utiliserais mon propre symbole de contrôle, pour le disassocier
d'assert. Mais on pourrait en discuter.

- J'ajouterais __FILE__ et __LINE__ automatiquement danse les
sorties.

- Dans le cas où le debug est active, j'ajouterais un test d'une
variable globale, du genre (cas C++) :

tracingOn
&& someDebugOutputStream
<< '[' << __FILE__
<< ':' << __LINE__
<< "]: "<< list << std::endl ;

Avec celà, et un moyen de positionner tracingOn, je crois qu'on a
déjà
un système suffisant pour une petite application. Pour des gros
applications, qui tournent sur une machine où il n'y a pas forcement
quelqu'un devant l'écran, on est souvent amené à faire plus complexe
:
il y a une dixaine de drapeaux (pour des niveaux différents de trace)
par sous-système (pour ne pas s'inonder des traces quand il y a un
problème dans un sous-système), avec différentes options de sortie
(vers
un fichier de log, un email à un administrateur, syslog sur des
systèmes
Unix, voire même une connection Tivoli ou d'autres serverurs SNMP. Et
si
c'est un serveur sur un système critique, on s'arrange à pouvoir
changer
la configuration de log sans arrêter le serveur.

Dans la pratique, le temps d'exécution du test, quand le tracing est
desactivé, et assez faible. Si le fait de pouvoir le supprimer
complètement a joué parfois dans la vente du système, pour qu'il
soit
accepté, il n'a jamais servi en pratique -- on a toujours livré avec
le
tracing compilé dans le code, et activable au moyen d'une option dans
la
ligne de commande ou dans le fichier de configuration.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
kanze
Olivier Azeau wrote:
Michael Moreno wrote:
Tu pratiques l'obfuscation active ?


Comment puis-je faire pour debugger sans utiliser la compilation
conditionnelle ?

chez moi ca s'arrete a :
#if_Debug
OutputDebugString("...");
#end if

Je ne veux pas aller plus loin que cela.


Dans ce cas, considère peut être qqe chose comme :

#define DEBUG_OUTPUT(data) { std::ostringstream s; s << data << 'n';

OutputDebugString(s.str().c_str()); }

DEBUG_OUTPUT( "..." << x << "..." );

qui est, à mon goût, plus lisible.


Pourquoi pas quelque chose du genre :

#define DEBUG( msg )
debugOutput != NULL
&& *debugOutput << '[' << __FILE__
<< ':' << __LINE__
<< "] " << msg
<< std::endl

Ce n'est pas particulièrement élégant, mais l'utilisation d'un nom
tout
en majuscules doit bien suffire pour signalier une utilisation un peu
particulier. C'est suffisant pour une petite application.

Sinon, j'utilise des wrappers de stream (voir OutputStreamWrapper dans
le code à mon site). Mais même ici, il y aurait prèsque toujours un
macro, afin d'insérer automatiquement __FILE__ et __LINE__. Donc,
quelque chose du genre :

#define DEBUG
Log::log( LOG_DEBUG, __FILE__, __LINE__ )
#define LOG
Log::log( LOG_LOG, __FILE__, __LINE__ )
#define ERROR(
Log::log( LOG_ERROR, __FILE__, __LINE__ )

(La fonction Log::log renvoie un flux « wrappé », ce qui permet des
choses comme :
DEBUG << "var = " << var ;
ou
ERROR << "c'est raté" ;
Même dans un environement multi-threadé, sans s'occuper des locks.)

Quelque soit la solution adoptée, il y aura un en-tête du genre
log.hh,
où les macros sont défini. Si jamais il fallait les supprimer
complètement pour des raisons de performance, c'est une version
différente de l'en-tête qui sert, choisi au moyen d'une option -I.
(Mais
ça ne m'est jamais arrivé, et dans les applications plus grosses, la
présence d'un log configurable fait souvent partie du cahier de
charges.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
Olivier Azeau
wrote:
(La fonction Log::log renvoie un flux « wrappé », ce qui permet des
choses comme :
DEBUG << "var = " << var ;
ou
ERROR << "c'est raté" ;


Le seul truc qui me gêne dans une telle solution c'est que les éléments
envoyés dans le flux vont toujours être évalués, même lorsque le log est
désactivé.

Même dans un environement multi-threadé, sans s'occuper des locks.)

Quelque soit la solution adoptée, il y aura un en-tête du genre
log.hh,
où les macros sont défini. Si jamais il fallait les supprimer
complètement pour des raisons de performance, c'est une version
différente de l'en-tête qui sert, choisi au moyen d'une option -I.
(Mais
ça ne m'est jamais arrivé, et dans les applications plus grosses, la
présence d'un log configurable fait souvent partie du cahier de
charges.)



Oui, mais il est des applications sur poste client que l'on veut les
plus légères possibles au niveau CPU (genre tache de fond avec zéro
impact sur l'utilisateur de la machine)

Avatar
drkm
Olivier Azeau writes:

wrote:

(La fonction Log::log renvoie un flux « wrappé », ce qui permet des
choses comme :
DEBUG << "var = " << var ;
ou
ERROR << "c'est raté" ;


Le seul truc qui me gêne dans une telle solution c'est que les éléments
envoyés dans le flux vont toujours être évalués, même lorsque le log est
désactivé.


Non, grâce au court-circuit de « && ».

--drkm


Avatar
Loïc Joly
Olivier Azeau wrote:

wrote:

(La fonction Log::log renvoie un flux « wrappé », ce qui permet des
choses comme :
DEBUG << "var = " << var ;
ou
ERROR << "c'est raté" ;



Le seul truc qui me gêne dans une telle solution c'est que les éléments
envoyés dans le flux vont toujours être évalués, même lorsque le log est
désactivé.


La même chose peut s'écrire un peu comme :

#define DEBUG if (!isLogEnabled) ; else Log::log(__FILE__, __LINE__ )

--
Loïc


Avatar
kanze
Olivier Azeau wrote:
wrote:
(La fonction Log::log renvoie un flux « wrappé », ce qui permet
des


choses comme :
DEBUG << "var = " << var ;
ou
ERROR << "c'est raté" ;


Le seul truc qui me gêne dans une telle solution c'est que les
éléments envoyés dans le flux vont toujours être évalués, m ême
lorsque

le log est désactivé.


Les expressions entre les << doivent être évaluées. Ce n'est
typiquement
pas un problème, parce que ce sont typiquement de simples lvalues ou
des
constantes de chaîne, dont l'évaluation ne coûte rien. En revanche,
ce
n'est pas difficile à éviter la conversion en texte, qui peut être
assez
chère parfois.

Même dans un environement multi-threadé, sans s'occuper des
locks.)



Quelque soit la solution adoptée, il y aura un en-tête du genre
log.hh, où les macros sont défini. Si jamais il fallait les
supprimer complètement pour des raisons de performance, c'est une
version différente de l'en-tête qui sert, choisi au moyen d'une
option -I. (Mais ça ne m'est jamais arrivé, et dans les
applications


plus grosses, la présence d'un log configurable fait souvent
partie


du cahier de charges.)


Oui, mais il est des applications sur poste client que l'on veut les
plus légères possibles au niveau CPU (genre tache de fond avec
zéro

impact sur l'utilisateur de la machine)


Donc ? Si le processus est en sommeil, le coût du logging est zéro.
Quand le processus se réveille, le coût dépend des appels -- en
gros, il
dépend plus ou moins de ce qu'on fait par ailleurs. Mais tant que le
logging est désactivé, le coût est très faible devant ce que tu
pourrais
vouloir faire d'autre -- si on loggue l'entrée et la sortie d'une
fonction virtuelle, par exemple, le coût de tester si le log est actif
est inférieur au coût de l'appel virtuel sur ma machine.

Si on active le log, évidemment, ça peut rallentir beaucoup. Mais si
on
a besoin de l'information, c'est un coût inévitable ; c'est toujours
beaucoup moins onéreux que de passer par un point d'arrêt dans un
deboggueur, et fait correctement, donne des informations plus
complètes
et plus pertinantes.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
drkm wrote:
Olivier Azeau writes:

wrote:

(La fonction Log::log renvoie un flux « wrappé », ce qui permet
des



choses comme :
DEBUG << "var = " << var ;
ou
ERROR << "c'est raté" ;


Le seul truc qui me gêne dans une telle solution c'est que les
éléments envoyés dans le flux vont toujours être évalués,
même


lorsque le log est désactivé.


Non, grâce au court-circuit de « && ».


Dans la version simplifiée. Dans le texte qu'il a cité, il est
question
d'un flux « wrappé », où il n'y a pas de « && ». D'après mes
expériences, ce n'est toujours pas un problème ; le << ressemble à :

template< typename T >
inline LogStream&
operator<<( LogStream& dest, T const& valeur )
{
std::ostream* s = dest.stream() ;
if ( s != NULL ) {
*s << valeur ;
}
return dest ;
}

La fonction LogStream::stream() est une fonction inline, qui renvoie
directement un membre du LogStream. Le coût est donc minime, parce
qu'il
n'y a pas de formattage si le log n'est pas actif. En fait, d'après
mon
expérience, le coût de la construction, la copie et la destruction
des
temporaires de LogStream est typiquement plus élevé.

Évidemment, un faible coût n'est pas zéro, et le cas échéant, rien
n'empèche de combiner les deux : de n'utiliser le LogStream temporaire
qu'après avoir vérifier que le log est actif. Parfois même, par
exemple
quand on veut sortir un tableau, c'est prèsque nécessaire :

if ( Log::isActive( Log::debug ) ) {
LogStream dest = Log::stream( Log::debug, __FILE__, __LINE__ )
;
dest << "En-tête de tableaun" ;
for ( Tableau::const_iterator i = t.begin() ;
i != t.end() ;
++ i ) {
dest << *i << 'n' ;
}
}

Pour ce genre de chose, le wrapper est prèsqu'essentiel -- c'est lui
qui
gère le lock, par exemple, et c'est lui que s'assure que la première
ligne contient l'en-tête d'un enrégistrement de log (avec le nom de
fichier et le numéro de ligne), que les autres lignes soient
indentées,
et qu'à la fin, le flux est correctement flushé, avec par exemple
l'envoi d'un email ou d'une entrée syslog, si c'est ce que la
configurati0n veut.

Mais évidemment, c'est un peu lourd pour beaucoup d'applications:-).

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
James Kanze
Loïc Joly wrote:
Olivier Azeau wrote:


wrote:



(La fonction Log::log renvoie un flux « wrappé », ce qui permet des
choses comme :
DEBUG << "var = " << var ;
ou
ERROR << "c'est raté" ;




Le seul truc qui me gêne dans une telle solution c'est que
les éléments envoyés dans le flux vont toujours être évalués,
même lorsque le log est désactivé.



La même chose peut s'écrire un peu comme :


#define DEBUG if (!isLogEnabled) ; else Log::log(__FILE__, __LINE__ )


Voir même
#define DEBUG isLogEnabled && Log::log( __FILE__, __LINE__ )
de façon à avoir une expression.

--
James Kanze home: www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34



Avatar
drkm
writes:

Évidemment, un faible coût n'est pas zéro, et le cas échéant, rien
n'empèche de combiner les deux : de n'utiliser le LogStream temporaire
qu'après avoir vérifier que le log est actif. Parfois même, par
exemple
quand on veut sortir un tableau, c'est prèsque nécessaire :

if ( Log::isActive( Log::debug ) ) {
LogStream dest = Log::stream( Log::debug, __FILE__, __LINE__ )
;
dest << "En-tête de tableaun" ;
for ( Tableau::const_iterator i = t.begin() ;
i != t.end() ;
++ i ) {
dest << *i << 'n' ;
}
}


Dans ce cas, je verrais bien, en utilisant Boost.Lambda, quelque
chose comme :

DEBUG
<< "Le tableau : "
<< LogForEach( t.begin() , t.end() , _1 << "n " << _2 ) ;

où « _1 » représente le flux de logging.

Toujours avec DEBUG défini comme :

#define DEBUG LogManager::enabled( LogManager::debug )
&& LogManager::stream( LogManager::debug )

--drkm

Avatar
kanze
drkm wrote:
writes:

Évidemment, un faible coût n'est pas zéro, et le cas échéant,
rien


n'empèche de combiner les deux : de n'utiliser le LogStream
temporaire qu'après avoir vérifier que le log est actif. Parfois
même, par exemple quand on veut sortir un tableau, c'est prèsque
nécessaire :

if ( Log::isActive( Log::debug ) ) {
LogStream dest = Log::stream( Log::debug, __FILE__, __LINE__ )
;


dest << "En-tête de tableaun" ;
for ( Tableau::const_iterator i = t.begin() ;
i != t.end() ;
++ i ) {
dest << *i << 'n' ;
}
}


Dans ce cas, je verrais bien, en utilisant Boost.Lambda, quelque
chose comme :

DEBUG
<< "Le tableau : "
<< LogForEach( t.begin() , t.end() , _1 << "n " << _2 ) ;

où « _1 » représente le flux de logging.

Toujours avec DEBUG défini comme :

#define DEBUG LogManager::enabled( LogManager::debug )
&& LogManager::stream( LogManager::debug )


Pourquoi faire simple quand on peut faire compliqué, n'est-ce pas ?

En fait, typiquement, les éléments du « tableau » n'ont pas
d'opérateur
<< défini -- selon le cas, il se peut qu'ils représente une jointure
de
plusieurs tableaux. Et évidemment, les tableaux sont privés à la
classe
où je suis -- si je voulait une classe externe pour les afficher, il
faudrait qu'elle soit amie (et donc, qu'elle soit connue dans
l'en-tête).

Avec un véritable lambda, qui permettait une fonction de plusieurs
lignes, ça serait peut-être jouable, mais je ne suis pas sûr. Je
suis
plutôt favorable à ajouter un lambda en C++, mais ce n'est pas une
solution à tout.

Ce qui serait pensable, c'est une fonction dumpTables(std::ostream&)
comme membre de la classe, et puis quelque chose du genre :

if ( Log::isActive( Log::debug ) ) {
dumpTables( Log::stream( Log::debug, __FILE__, __LINE__ ) ) ;
}

*Si* il s'avérait qu'on faisait quelque chose du genre assez souvent,
on
pourrait envisager un macro du genre

#define DEBUG_USE_FUNCTION( function )
Log::isActive( Log::debug )
&& function(
*Log::stream( Log::debug, __FILE__, __LINE__ ).stream()
)

Il faut dire que dans quinze ans d'expérience professionnelle avec
C++,
j'ai eu l'occasion de faire ce genre de chose une seule fois. Et
justement, cette fois-là, le « tableau » était un jointure, il n'y
avait
pas d'opérateur << prédéfini, et en fait, dans la boucle, il m'a
fallu 4
ou 5 lignes pour formatter comme j'ai voulu. Évidemment, si tes
expériences sont différentes, et que tu trouves que tu en as besoin
assez souvent, ça vaudrait la peine d'investir pour trouver une
solution
plus simple et plus générique. Mais dans mon cas, pour une fois tous
les
quinze ans...

Quelque soit la solution « générale » adoptée, je le trouve utile
de
définir aussi le niveau plus bas, pour permettre de traiter les cas
spéciaux, quelques ils soient.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


1 2