Est-il préférable d'utiliser la directive du préprocesseur ou l'instruction if (constante)?

10

Disons que nous avons une base de code qui est utilisée pour de nombreux clients différents, et que nous avons du code qui n'est pertinent que pour les clients de type X. Est-il préférable d'utiliser des directives de préprocesseur pour inclure ce code uniquement dans le client de type X, ou utiliser des instructions if? Pour être plus clair:

// some code
#if TYPE_X_COSTUMER  = 1
// do some things
#endif
// rest of the code

ou

if(TYPE_X_COSTUMER) {
    // do some things
}

Les arguments auxquels je peux penser sont:

  • La directive du préprocesseur entraîne une empreinte de code plus petite et moins de branches (sur les compilateurs non optimisants)
  • Si les instructions résultent avec du code qui se compile toujours, par exemple si quelqu'un fait une erreur qui endommagera le code non pertinent pour le projet sur lequel il travaille, l'erreur apparaîtra toujours et il ne corrompra pas la base de code. Sinon, il ne sera pas au courant de la corruption.
  • On m'a toujours dit de préférer l'utilisation du processeur à l'utilisation du préprocesseur (si c'est un argument du tout ...)

Qu'est-ce qui est préférable - quand on parle d'une base de code pour de nombreux clients différents?

MByD
la source
3
Quelles sont les chances que vous expédiez une version non créée à l'aide d'un compilateur d'optimisation? Et si cela se produit, l'impact aura-t-il de l'importance (surtout lorsqu'il sera mis en contexte avec toutes les autres optimisations que vous manquerez alors)?
@delnan - Le code est utilisé pour de nombreuses plates-formes différentes avec de nombreux compilateurs différents. Et en ce qui concerne l'impact - il pourrait dans certains cas.
MByD
On vous a dit de préférer quelque chose? C'est comme forcer quelqu'un à manger de la nourriture du KFC alors qu'il n'aime pas le poulet.
droite
@WTP - non je n'étais pas, je suis intéressé à en savoir plus, donc je prendrais de meilleures décisions à l'avenir.
MByD
Dans votre deuxième exemple, est-ce TYPE_X_CUSTOMERtoujours une macro de préprocesseur?
detly

Réponses:

5

Je pense qu'il y a un avantage à utiliser un #define que vous n'avez pas mentionné, et c'est le fait que vous pouvez définir la valeur sur la ligne de commande (ainsi, définissez-la à partir de votre script de construction en une étape).

En dehors de cela, il est généralement préférable d'éviter les macros. Ils ne respectent aucune portée et cela peut poser des problèmes. Seuls les compilateurs très stupides ne peuvent pas optimiser une condition basée sur une constante de temps de compilation. Je ne sais pas si c'est une préoccupation pour votre produit (par exemple, il pourrait être important de garder le code petit sur les plates-formes embarquées).

Tamás Szelei
la source
En fait, c'est pour les systèmes embarqués
MByD
Le problème est que les systèmes embarqués utilisent généralement un ancien compilateur (comme par exemple gcc 2.95).
BЈовић
@VJo - GCC est le cas chanceux :)
MByD
Essayez-le ensuite. S'il comprend le code inutilisé et que sa taille est considérable, optez pour les définitions.
Tamás Szelei
Si vous voulez pouvoir le définir via la ligne de commande du compilateur, j'utiliserais toujours une constante dans le code lui-même et ne définirais que la valeur attribuée à cette constante.
CodesInChaos
12

Comme pour de nombreuses questions, la réponse à cette question est que cela dépend . Au lieu de dire ce qui est mieux, j'ai plutôt donné des exemples et des objectifs où l'un est meilleur que l'autre.

Le préprocesseur et la constante ont leurs propres emplacements d'utilisation appropriés.

En cas de pré-processeur, le code est supprimé avant l'heure de compilation. Par conséquent, il est mieux adapté aux situations où le code ne devrait pas être compilé . Cela peut affecter la structure du module, les dépendances et permettre de sélectionner les meilleurs segments de code pour les aspects de performances. Dans les cas suivants, il faut diviser le code en utilisant uniquement le préprocesseur.

  1. Code multiplateforme:
    par exemple, lorsque le code est compilé sur différentes plates-formes, lorsque le code dépend de numéros de version spécifiques du système d'exploitation (ou même de la version du compilateur - bien que cela soit très rare). Par exemple, lorsque vous traitez avec des contreparties de code petit-endien big-endien - elles doivent être séparées avec des préprocesseurs plutôt qu'avec des constantes. Ou si vous compilez du code pour Windows ainsi que Linux et certains appels système sont très différents.

  2. Patchs expérimentaux:
    Un autre cas où cela fait est d'avoir un code expérimental justifié qui est risqué ou certains modules majeurs qui doivent être omis qui auront une liaison significative ou une différence de performance. La raison pour laquelle on voudrait désactiver le code via un préprocesseur plutôt que de se cacher sous if () est parce que nous ne pouvons pas être sûrs des bogues introduits par cet ensemble de modifications spécifique et que nous fonctionnons sous une base expérimentale. S'il échoue, nous ne devons rien faire d'autre que désactiver ce code en production que réécrire. Quelque temps, il est idéal d'utiliser #if 0 pour commenter le code entier.

  3. Gérer les dépendances: une
    autre raison pour laquelle vous voudrez peut-être générer Par exemple, si vous ne voulez pas prendre en charge les images JPEG, vous pouvez vous débarrasser de la compilation de ce module / stub et éventuellement la bibliothèque ne sera pas (statiquement ou dynamiquement) liée à cela module. Parfois, les packages s'exécutent ./configurepour identifier la disponibilité d'une telle dépendance et si les bibliothèques ne sont pas présentes (ou l'utilisateur ne veut pas l'activer), cette fonctionnalité est désactivée automatiquement sans lien avec cette bibliothèque. Ici, il est toujours avantageux que ces directives soient générées automatiquement.

  4. Licence:
    un exemple très intéressant de directive de préprocesseur est ffmpeg . Il possède des codecs qui peuvent potentiellement enfreindre les brevets par son utilisation. Si vous téléchargez la source et compilez pour l'installer, il vous demande si vous voulez ou éloignez ce genre de choses. Garder les codes cachés sous certaines conditions si les conditions peuvent encore vous amener en cour!

  5. Copier-coller de code:
    macros Aka. Ce n'est pas un conseil pour une utilisation excessive des macros - juste que les macros ont un moyen beaucoup plus puissant d'appliquer l' équivalent copié- collé. Mais utilisez-le avec grand soin; et utilisez-le si vous savez ce que vous faites. Les constantes, bien sûr, ne peuvent pas faire cela. Mais on peut aussi utiliser des inlinefonctions si c'est facile à faire.

Alors, quand utilisez-vous des constantes?
Presque partout ailleurs.

  1. Flux de code plus net:
    en général, lorsque vous utilisez des constantes, il est presque impossible de les distinguer des variables régulières et, par conséquent, il s'agit d'un code mieux lisible. Si vous écrivez une routine de 75 lignes, ayez 3 ou 4 lignes toutes les 10 lignes avec #ifdef est TRÈS incapable de lire . Probablement donné une constante primaire régie par #ifdef, et utilisez-la dans un flux naturel partout.

  2. Code bien indenté: Toutes les directives de préprocesseur, ne fonctionnent jamais bien avec du code autrement bien indenté . Même si votre compilateur autorise l'indentation de #def, le préprocesseur pré-ANSI C ne permettait pas d'espace entre le début d'une ligne et le caractère "#"; le "#" de tête devait toujours être placé dans la première colonne.

  3. Configuration:
    Une autre raison pour laquelle les constantes / ou les variables ont un sens est qu'elles peuvent facilement évoluer en étant liées à des globales ou à l'avenir peuvent être étendues pour être dérivées de fichiers de configuration.

Une dernière chose:
ne jamais utiliser de directives de préprocesseur #ifdefpour #endif traverser la portée ou { ... }. c'est-à-dire début #ifdefou fin de #endifdifférents côtés de { ... }. C'est extrêmement mauvais; cela peut être déroutant, parfois dangereux.

Bien sûr, ce n'est pas une liste exhaustive, mais vous montre une nette différence, où la méthode est plus apte à utiliser. Il ne s'agit pas vraiment de savoir ce qui est le mieux , c'est toujours plus dont on est plus naturel à utiliser dans un contexte donné.

Dipan Mehta
la source
Merci pour la réponse longue et détaillée. J'étais au courant de la plupart des choses que vous avez mentionnées et elles ont été omises de la question car la question n'est pas de savoir s'il faut utiliser des capacités de préprocesseur du tout, mais d'un type spécifique d'utilisation. Mais je pense que le point fondamental que vous avez soulevé est vrai.
MByD
1
"N'UTILISEZ JAMAIS les directives de préprocesseur #ifdef à #endif traversant la portée ou {...}" cela dépend de l'IDE. Si l'EDI reconnaît les blocs de préprocesseur inactifs et les réduit ou les affiche avec des couleurs différentes, ce n'est pas déroutant.
Abyx
@Abyx c'est vrai que l'IDE peut aider. Cependant, dans de nombreux cas où les gens se développent sous la plate-forme * nix (linux, etc.) - le choix des éditeurs, etc. sont nombreux (VI, emacs, etc.). Il se peut donc que quelqu'un d'autre utilise un éditeur différent du vôtre.
Dipan Mehta
4

Vos clients ont-ils accès à votre code? Si oui, le préprocesseur pourrait être une meilleure option. Nous avons quelque chose de similaire et nous utilisons des indicateurs de temps de compilation pour divers clients (ou des fonctionnalités spécifiques au client). Ensuite, un script pourrait extraire le code spécifique au client et nous expédions ce code. Les clients ne connaissent pas les autres clients ou d'autres fonctionnalités spécifiques au client.

Aditya Sehgal
la source
3

Quelques points supplémentaires dignes de mention:

  1. Le compilateur peut traiter certains types d'expressions constantes que le préprocesseur ne peut pas. Par exemple, le préprocesseur n'aura généralement aucun moyen d'évaluer sizeof()les types non primitifs. Cela peut forcer l'utilisation de if()dans certains cas.

  2. Le compilateur ignorera la plupart des problèmes de syntaxe dans le code qui est ignoré #if(certains problèmes liés au préprocesseur peuvent toujours causer des problèmes) mais insistera sur l'exactitude syntaxique du code ignoré if(). Ainsi, si le code qui est ignoré pour certaines versions mais pas pour d'autres devient invalide à la suite de modifications ailleurs dans le fichier (par exemple, les identifiants renommés), il squawk généralement sur toutes les versions s'il est désactivé avec if()mais pas s'il est désactivé par #if. Selon la raison pour laquelle le code est ignoré, cela peut ou non être une bonne chose.

  3. Certains compilateurs omettent le code inaccessible tandis que d'autres non; cependant, les déclarations de durée de stockage statique dans le code inaccessible, y compris les littéraux de chaîne, peuvent allouer de l'espace même si aucun code ne peut jamais utiliser l'espace ainsi alloué.

  4. Les macros peuvent utiliser des if()tests en leur sein, mais il n'y a hélas aucun mécanisme permettant au préprocesseur d'exécuter une logique conditionnelle au sein d'une macro [notez que pour une utilisation dans des macros, l' ? :opérateur peut souvent être meilleur que if, mais les mêmes principes s'appliquent].

  5. La #ifdirective peut être utilisée pour contrôler les macros définies.

Je pense que if()c'est souvent plus propre pour la plupart des cas, sauf ceux qui impliquent de prendre des décisions en fonction de ce que le compilateur utilise ou des macros à définir; certains des problèmes ci-dessus peuvent nécessiter l'utilisation #if, cependant, même dans les cas où ifcela semble plus propre.

supercat
la source
1

Pour faire court: les craintes concernant les directives de préprocesseur sont essentiellement 2, de multiples inclusions et peut-être du code moins lisible et une logique plus cryptique.

Si votre projet est petit avec presque aucun grand plan pour l'avenir, je pense que les deux options offrent le même rapport entre les avantages et les inconvénients, mais si votre projet va être énorme, envisagez autre chose comme l'écriture d'un fichier d'en-tête séparé si vous voulez toujours utiliser des directives de préprocesseur, mais gardez à l'esprit que ce type d'approche rend généralement le code moins lisible et assurez-vous que le nom de ces constantes signifie quelque chose pour la logique métier du programme lui-même.

Micro
la source
Il s'agit d'une excellente base de code, et les définitions sont en effet dans certains en-têtes distincts.
MByD