web-dev-qa-db-fra.com

#pragma une fois vs inclure des gardes?

Je travaille sur une base de code connue pour ne fonctionner que sous Windows et être compilée sous Visual Studio (elle s'intègre étroitement à Excel pour ne pas aller nulle part). Je me demande si je devrais utiliser les gardes traditionnels ou utiliser _#pragma once_ pour notre code. Je pense que laisser le compilateur traiter avec _#pragma once_ permettra d'obtenir des compilations plus rapides et sera moins sujet aux erreurs lors du copier-coller. C'est aussi un peu moins moche ;)

Remarque: pour obtenir des temps de compilation plus rapides, nous pourrions utiliser gardes inclus redondants , mais cela ajoute un couplage étroit entre le fichier inclus et le fichier inclus. D'habitude c'est correct car la protection devrait être basée sur le nom du fichier et ne changerait que si vous deviez changer le nom d'inclusion de toute façon.

331
Matt Price

Je ne pense pas que cela fera une différence significative en termes de temps de compilation mais #pragma once est très bien supporté par les compilateurs mais ne fait pas vraiment partie de la norme. Le pré-processeur peut être un peu plus rapide car il est plus simple de comprendre votre intention exacte.

#pragma once est moins enclin à commettre des erreurs et c’est moins de code à taper.

Pour accélérer le temps de compilation, il suffit de déclarer en amont au lieu d’inclure dans les fichiers .h dès que vous le pouvez.

Je préfère utiliser #pragma once.

Voir ceci article de wikipedia sur la possibilité d'utiliser les deux .

290
Brian R. Bondy

Je voulais juste ajouter à cette discussion que je ne fais que compiler sur VS et GCC, et que j'utilisais autrefois des gardes. Je suis maintenant passé à #pragma once, et la seule raison pour laquelle je ne me soucie pas de la performance, de la portabilité ou du standard, car je ne me soucie pas vraiment de ce qui est standard tant que VS et GCC le prennent en charge, à savoir:

#pragma once réduit les risques de bugs.

Il est trop facile de copier et coller un fichier d’en-tête dans un autre fichier d’en-tête, de le modifier pour répondre à vos besoins et d’oublier de changer le nom du garde d’inclusion. Une fois les deux inclus, il vous faut un certain temps pour localiser l'erreur, car les messages d'erreur ne sont pas nécessairement clairs.

160
Cookie

#pragma once a des bogues non résolus . Il ne devrait jamais être utilisé.

Si votre chemin de recherche #include est suffisamment compliqué, le compilateur peut ne pas être en mesure de faire la différence entre deux en-têtes avec le même nom de base (par exemple a/foo.h et b/foo.h), donc un #pragma once dans l'un d'eux supprimera les deux . Il peut également ne pas être en mesure de dire que deux inclusions relatives différentes (par exemple, #include "foo.h" et #include "../a/foo.h" se rapportent au même fichier. Par conséquent, #pragma once ne réussira pas à supprimer une inclusion redondante au moment opportun.

Cela affecte également la capacité du compilateur à éviter de relire des fichiers avec les gardes #ifndef, mais il ne s'agit que d'une optimisation. Avec les gardes #ifndef, le compilateur peut lire en toute sécurité tout fichier qu’il n’est pas sûr d’avoir déjà vu; si c'est faux, il suffit de faire un travail supplémentaire. Tant qu'il n'y a pas deux en-têtes définissant la même macro de garde, le code sera compilé comme prévu. Et si deux en-têtes définissent la même macro de garde , le programmeur peut y entrer et en changer une.

#pragma once n'a pas un tel réseau de sécurité - si le compilateur se trompe sur l'identité d'un fichier d'en-tête, dans les deux cas , le programme échouera compiler. Si vous rencontrez ce bogue, votre seule option consiste à cesser d'utiliser #pragma once, ou à renommer l'un des en-têtes. Les noms des en-têtes font partie de votre contrat d’API. Le renommage n’est donc probablement pas une option.

(La version courte expliquant pourquoi ceci est non corrigible est que ni l'API Unix ni l'API du système de fichiers Windows n'offrent un mécanisme qui garanties pour vous indiquer si deux chemins d'accès absolus font référence au même fichier. Si vous avez l'impression que les numéros d'inode peuvent être utilisés à cette fin, désolé, vous vous trompez.)

(Note historique: La seule raison pour laquelle je n’ai pas extrait #pragma once et #import de GCC alors que j’en étais autorisé, il ya environ 12 ans, c’est que les en-têtes système d’Apple s’en remettaient à eux. Rétrospectivement , cela n’aurait pas dû me arrêter.)

(Depuis que cela a été soulevé deux fois dans le fil de commentaire: les développeurs de GCC ont déployé des efforts considérables pour rendre #pragma once aussi fiable que possible; voir rapport de bogue GCC 11569 . Cependant, l'implémentation dans les versions actuelles de GCC peut toujours échouer dans des conditions plausibles, telles que la construction de batteries de serveurs souffrant d'horloge asymétrique. Je ne sais pas ce que l'implémentation d'un autre compilateur est comme, mais je ne m'attendrais pas à ce que quelqu'un ait fait mieux .)

108
zwol

Jusqu'au jour où #pragma once devient standard (ce n'est pas une priorité pour les normes à venir), je vous suggère de l'utiliser ET d'utiliser des protections, de la manière suivante:

#ifndef BLAH_H
#define BLAH_H
#pragma once

// ...

#endif

Les raisons sont:

  • #pragma once n'est pas standard, il est donc possible que certains compilateurs ne fournissent pas cette fonctionnalité. Cela dit, tous les compilateurs majeurs le supportent. Si un compilateur ne le sait pas, au moins il sera ignoré.
  • Comme il n'y a pas de comportement standard pour #pragma once, vous ne devez pas supposer que le comportement sera le même sur tous les compilateurs. Les gardes veilleront au moins à ce que l'hypothèse de base soit la même pour tous les compilateurs qui implémentent au moins les instructions de pré-traitement nécessaires pour les gardes.
  • Sur la plupart des compilateurs, #pragma once accélérera la compilation (d'un cpp) car le compilateur ne rouvrira pas le fichier contenant cette instruction. Donc, l'avoir dans un fichier peut aider ou non, selon le compilateur. J'ai entendu dire que g ++ peut faire la même optimisation lorsque des gardes sont détectés, mais cela doit être confirmé.

En utilisant les deux ensemble, vous obtenez le meilleur de chaque compilateur pour cela.

Maintenant, si vous n'avez pas de script automatique pour générer les gardes, il pourrait être plus pratique d'utiliser simplement #pragma once. Sachez simplement ce que cela signifie pour le code portable. (J'utilise VAssistX pour générer les gardes et le pragma une fois rapidement)

Vous devriez presque toujours penser votre code de manière portable (parce que vous ne savez pas de quoi l'avenir est fait) mais si vous pensez vraiment qu'il n'est pas destiné à être compilé avec un autre compilateur (code pour du matériel embarqué très spécifique par exemple) alors vous devriez juste consulter la documentation de votre compilateur sur #pragma once pour savoir ce que vous faites vraiment.

35
Klaim

Du point de vue d'un testeur de logiciel

#pragma once est plus court qu'une protection include, moins sujet aux erreurs, supporté par la plupart des compilateurs, et certains disent qu'il compile plus rapidement (ce qui n'est plus le cas [aujourd'hui]).

Mais je suggère tout de même que vous utilisiez la norme #ifndef include include.

Pourquoi #ifndef?

Considérons une hiérarchie de classes artificielle comme celle-ci où chacune des classes A, B et C vit dans son propre fichier:

a.h

#ifndef A_H
#define A_H

class A {
public:
  // some virtual functions
};

#endif

b.h

#ifndef B_H
#define B_H

#include "a.h"

class B : public A {
public:
  // some functions
};

#endif

c.h

#ifndef C_H
#define C_H

#include "b.h"

class C : public B {
public:
  // some functions
};

#endif

Supposons maintenant que vous écrivez des tests pour vos classes et que vous deviez simuler le comportement de la classe très complexe B. Une façon de le faire serait d'écrire un classe fictive en utilisant par exemple google mock et de le placer dans un répertoire mocks/b.h. Notez que le nom de la classe n'a pas changé mais qu'il est uniquement stocké dans un autre répertoire. Mais le plus important, c’est que la protection d’inclusion porte exactement le même nom que dans le fichier original b.h.

mock/b.h

#ifndef B_H
#define B_H

#include "a.h"
#include "gmock/gmock.h"

class B : public A {
public:
  // some mocks functions
  MOCK_METHOD0(SomeMethod, void());
};

#endif

Quel est l'avantage?

Avec cette approche, vous pouvez simuler le comportement de la classe B sans toucher à la classe d'origine ni en parler à C. Tout ce que vous avez à faire est de placer le répertoire mocks/ dans le chemin d’inclusion de votre fournisseur.

Pourquoi cela ne peut-il pas être fait avec #pragma once?

Si vous aviez utilisé #pragma once, vous auriez un conflit de noms car il ne peut pas vous empêcher de définir la classe B deux fois, une fois la version originale et une fois fausse.

32
Konrad Kleine

Si vous êtes certain que vous n'utiliserez jamais ce code dans un compilateur qui ne le prend pas en charge (Windows/VS, GCC et Clang sont des exemples de compilateurs que do le supportent), vous pouvez alors: utilisez certainement #pragma une fois sans soucis.

Vous pouvez également simplement utiliser les deux (voir exemple ci-dessous) pour obtenir la portabilité et l'accélération de la compilation sur des systèmes compatibles.

#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_

...

#endif
22
Donnie DeBoer

Après avoir longuement discuté du compromis de performance supposé entre #pragma once et #ifndef gardes et argument de correction ou non (je prenais le parti de #pragma once sur la base d'un endoctrinement relativement récent à cette fin), j'ai finalement décidé de tester la théorie selon laquelle #pragma once est plus rapide, car le compilateur n'a pas à essayer de re#include un fichier qui a déjà été inclus.

Pour le test, j'ai généré automatiquement 500 fichiers d'en-tête avec des interdépendances complexes et un fichier .c qui #includes tous. J'ai exécuté le test de trois manières, une fois avec #ifndef, une fois avec #pragma once et une fois avec les deux. J'ai effectué le test sur un système assez moderne (un MacBook Pro 2014 fonctionnant sous OSX, utilisant Clang fourni avec XCode, avec le SSD interne).

Tout d'abord, le code de test:

#include <stdio.h>

//#define IFNDEF_GUARD
//#define PRAGMA_ONCE

int main(void)
{
    int i, j;
    FILE* fp;

    for (i = 0; i < 500; i++) {
        char fname[100];

        snprintf(fname, 100, "include%d.h", i);
        fp = fopen(fname, "w");

#ifdef IFNDEF_GUARD
        fprintf(fp, "#ifndef _INCLUDE%d_H\n#define _INCLUDE%d_H\n", i, i);
#endif
#ifdef PRAGMA_ONCE
        fprintf(fp, "#pragma once\n");
#endif


        for (j = 0; j < i; j++) {
            fprintf(fp, "#include \"include%d.h\"\n", j);
        }

        fprintf(fp, "int foo%d(void) { return %d; }\n", i, i);

#ifdef IFNDEF_GUARD
        fprintf(fp, "#endif\n");
#endif

        fclose(fp);
    }

    fp = fopen("main.c", "w");
    for (int i = 0; i < 100; i++) {
        fprintf(fp, "#include \"include%d.h\"\n", i);
    }
    fprintf(fp, "int main(void){int n;");
    for (int i = 0; i < 100; i++) {
        fprintf(fp, "n += foo%d();\n", i);
    }
    fprintf(fp, "return n;}");
    fclose(fp);
    return 0;
}

Et maintenant, mes différents tests:

folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.164s
user    0m0.105s
sys 0m0.041s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.140s
user    0m0.097s
sys 0m0.018s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.193s
user    0m0.143s
sys 0m0.024s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.153s
user    0m0.101s
sys 0m0.031s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.170s
user    0m0.109s
sys 0m0.033s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.155s
user    0m0.105s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.153s
user    0m0.101s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.181s
user    0m0.133s
sys 0m0.020s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.167s
user    0m0.119s
sys 0m0.021s
folio[~/Desktop/pragma] fluffy$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-Apple-darwin17.0.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

Comme vous pouvez le constater, les versions avec #pragma once étaient en effet légèrement plus rapides à prétraiter que le #ifndef- un seul, mais la différence était assez négligeable, et serait largement occultée par le temps nécessaire pour créer et relier le code. Peut-être qu’avec une base de code assez grande, les temps de construction seraient différents de quelques secondes, mais entre les compilateurs modernes capables d’optimiser les gardes #ifndef, le fait que les OS ont de bons caches de disque et les vitesses technologie de stockage, il semble que l’argument des performances soit discutable, du moins sur un système de développement typique de nos jours. Les environnements de construction plus anciens et plus exotiques (par exemple, les en-têtes hébergés sur un partage réseau, la construction à partir d'une bande, etc.) peuvent quelque peu changer l'équation, mais dans ces circonstances, il semble plus utile de créer simplement un environnement de construction moins fragile.

Le fait est que #ifndef est normalisé avec le comportement standard alors que #pragma once ne l’est pas, et #ifndef gère également les cas étranges de système de fichiers et de chemin de recherche alors que #pragma once peut être très complexe. confondu par certaines choses, conduisant à un comportement incorrect sur lequel le programmeur n'a aucun contrôle. Le principal problème de #ifndef est que les programmeurs choisissent des noms incorrects pour leurs gardes (avec des collisions de noms, etc.) et que même dans ce cas, il est tout à fait possible pour le consommateur d'une API de remplacer ces mauvais noms en utilisant #undef - not une solution parfaite peut-être, mais c'est possible, alors que #pragma once n'a aucun recours si le compilateur élimine à tort un #include.

Ainsi, même si#pragma once est manifestement (légèrement) plus rapide, je ne pense pas que cela soit en soi une raison de l'utiliser sur #ifndef gardes.

EDIT: Grâce aux commentaires de @LightnessRacesInOrbit, j'ai augmenté le nombre de fichiers d'en-tête et modifié le test pour n'exécuter que l'étape du préprocesseur, ce qui élimine le peu de temps ajouté par la compilation. et processus de lien (qui était trivial auparavant et inexistant maintenant). Comme prévu, le différentiel est à peu près le même.

19
fluffy

En général, je ne m'embête pas avec #pragma once car mon code doit parfois être compilé avec autre chose que MSVC ou GCC (les compilateurs pour les systèmes embarqués n'ont pas toujours le # pragma).

Je dois donc utiliser des gardes #include de toute façon. Je pourrais aussi utiliser #pragma once comme le suggèrent certaines réponses, mais il ne semble pas y avoir beaucoup de raisons et cela causera souvent des avertissements inutiles sur les compilateurs qui ne le supportent pas.

Je ne suis pas sûr du gain de temps que le pragma pourrait apporter. J'ai entendu dire que les compilateurs reconnaissent généralement déjà lorsqu'un en-tête ne contient que des commentaires en dehors des macros de garde et qu'il fait l'équivalent #pragma once dans ce cas (c'est-à-dire qu'il ne traite plus jamais le fichier). Mais je ne sais pas si c'est vrai ou s'il s'agit juste de compilateurs pourrait faire cette optimisation.

Dans les deux cas, il est tout simplement plus facile pour moi d’utiliser des gardes #include qui fonctionneront partout et ne vous inquiétez plus.

15
Michael Burr

Il y a une question connexe à laquelle j'ai répond :

#pragma once a un inconvénient (autre que d'être non standard): si vous avez le même fichier à des emplacements différents (nous l'avons parce que notre système de compilation copie des fichiers), le compilateur pensera qu'il s'agit de fichiers différents. .

J'ajoute la réponse ici aussi au cas où quelqu'un trébuche sur cette question et non l'autre.

10
Motti

Je pense que la première chose à faire est de vérifier si cela va vraiment faire la différence, c.-à-d. vous devriez d'abord tester la performance. Une des recherches dans Google a jeté this .

Dans la page des résultats, les colonnes sont légèrement décalées pour moi, mais il est clair qu'au moins jusqu'à VC6, Microsoft n'implémentait pas les optimisations d'inclusion incluses que les autres outils utilisaient. Lorsque le garde d’inclusion était interne, il a fallu 50 fois plus de temps que pour le garde d’inclusion externe (les gardes d’extérieur sont au moins aussi bons que # pragma). Mais considérons l’effet possible de ceci:

Selon les tableaux présentés, le temps nécessaire pour ouvrir l’inclusion et le vérifier est 50 fois supérieur à celui d’un équivalent #pragma. Mais le temps réel pour le faire a été mesuré à 1 microseconde par fichier en 1999!

Ainsi, combien d'en-têtes en double un seul TU aura-t-il? Cela dépend de votre style, mais si nous disons qu'une TU moyenne a 100 doublons, alors en 1999, nous payons potentiellement 100 microsecondes par TU. Avec les améliorations apportées au disque dur, ce chiffre est probablement bien inférieur à présent, mais même avec des en-têtes précompilés et une dépendance correcte, le suivi du coût total cumulé de ce projet pour un projet représente certainement une partie insignifiante de votre temps de génération.

Aussi peu probable que cela puisse paraître, si vous passez à un compilateur ne prenant pas en charge #pragma once, réfléchissez au temps qu'il faudra pour mettre à jour l'ensemble de votre base source afin d'inclure plutôt des gardes. que #pragma?

Il n'y a aucune raison pour que Microsoft ne puisse pas implémenter une optimisation include guard de la même manière que GCC et tous les autres compilateurs (quelqu'un peut-il réellement confirmer si leurs versions les plus récentes l'implémentent?). IMHO, #pragma once ne fait pas autre chose que de limiter votre choix de compilateur alternatif.

9
Richard Corden

#pragma once permet au compilateur d'ignorer complètement le fichier lorsqu'il se reproduit - au lieu d'analyser le fichier jusqu'à ce qu'il atteigne les gardes #include.

En tant que telles, la sémantique est un peu différente, mais elles sont identiques si elles sont utilisées comme elles sont destinées à être utilisées.

Combiner les deux est probablement la voie la plus sûre, car dans le pire des cas (un compilateur signalant les pragmas inconnus comme des erreurs réelles, pas seulement des avertissements), il vous suffirait de supprimer les # pragma eux-mêmes.

Lorsque vous limitez vos plates-formes à, par exemple, "Compilateurs traditionnels sur le bureau", vous pouvez omettre en toute sécurité les gardes #include, mais je me sens mal à l'aise à ce sujet également.

OT: si vous avez d'autres astuces/expériences à partager sur l'accélération des builds, je serais curieux.

4
peterchen

Pour ceux qui souhaitent utiliser #pragma une fois et inclure les gardes ensemble: Si vous n'utilisez pas MSVC, vous n'obtiendrez pas beaucoup d'optimisation de #pragma une fois.

Et vous ne devriez pas mettre "#pragma once" dans un en-tête censé être inclus plusieurs fois, chaque inclusion pouvant avoir un effet différent.

Ici est une discussion détaillée avec des exemples sur #pragma une fois utilisation.

1
Deqing

Au sommet de l'explication par Konrad Kleine ci-dessus.

Un bref résumé:

  • lorsque nous utilisons # pragma once, le compilateur a la responsabilité de ne pas permettre son inclusion plus d'une fois. Ce qui signifie qu'après que vous ayez mentionné l'extrait de code dans le fichier, ce n'est plus votre responsabilité.

Maintenant, le compilateur recherche cet extrait de code au début du fichier et évite son inclusion (si déjà inclus une fois). Cela réduira définitivement le temps de compilation (en moyenne et dans un système énorme). Cependant, dans le cas de simulations/environnement de test, la mise en œuvre des cas de test sera difficile, en raison de dépendances circulaires, etc.

  • Désormais, lorsque nous utilisons le #ifndef XYZ_H pour les en-têtes, il incombe davantage aux développeurs de maintenir la dépendance des en-têtes. Ce qui signifie que, chaque fois qu’un nouveau fichier d’en-tête est créé, il est possible que la dépendance soit circulaire, le compilateur marquera simplement quelques messages d’erreur "undefined .." lors de la compilation, et il appartient à l’utilisateur de vérifier la connexion/le flux logique du fichier. entités et rectifier l'inapproprié comprend.

Cela va certainement ajouter au temps de compilation (car il faut rectifier et ré-exécuter). En outre, comme cela fonctionne sur la base de l'inclusion du fichier, basé sur l'état défini "XYZ_H", et se plaint toujours, si pas en mesure d'obtenir toutes les définitions.

Par conséquent, pour éviter de telles situations, nous devrions utiliser,

#pragma once
#ifndef XYZ_H
#define XYZ_H
...
#endif

c'est-à-dire la combinaison des deux.

1
parasrish