web-dev-qa-db-ja.com

#pragmaかつてガードを含む?

私は、Windows上でのみ動作し、Visual Studioでコンパイルされることが知られているコードベースに取り組んでいます(Excelと緊密に統合されているので、どこにも行きません)。私は伝統的なインクルードガードを使うべきか、私たちのコードに#pragma onceを使うべきか疑問に思います。コンパイラに#pragma onceを処理させるとコンパイルが速くなり、コピー&ペースト時にエラーが発生しにくくなります。また、やや見にくいです;)

注:コンパイル時間を短縮するには、 冗長インクルードガード を使用できますが、インクルードファイルとインクルードファイルの間に密接な関係があります。ガードはファイル名に基づくべきで、インクルード名を変更する必要がある場合にのみ変更されるので、通常は問題ありません。

324
Matt Price

私はそれがコンパイル時間に大きな違いを生むとは思わないが、#pragma onceはコンパイラ間で非常によくサポートされていますが、実際には標準の一部ではありません。あなたの正確な意図を理解するのがより簡単であるので、プリプロセッサはそれで少し速いかもしれません。

#pragma onceは間違いを犯しにくく、入力するコードも少なくなります。

可能であれば、コンパイル時間をもっと短縮するには、.hファイルに含めるのではなく、前方宣言を行ってください。

私は#pragma onceを使うのが好きです。

これを参照してください 両方を使用する可能性についてのウィキペディアの記事

283
Brian R. Bondy

私はVSとGCCでコンパイルしていて、かつてインクルードガードを使用していたことをこの議論に追加したいだけでした。私は今#pragma onceに切り替えました、そして私の唯一の理由は私がVSとGCCがそれをサポートする限り標準であるものを本当に気にしないので性能または移植性または標準ではありません、そしてそれはそれです:

#pragma onceはバグの可能性を減らします。

ヘッダーファイルを別のヘッダーファイルにコピーアンドペーストし、必要に応じて修正し、インクルードガードの名前を変更することを忘れないでください。両方が含まれると、エラーメッセージが必ずしも明確ではないため、エラーを追跡するのにしばらく時間がかかります。

157
Cookie

#pragma once修正不可能なバグがあります。絶対に使わないでください。

あなたの#include検索パスが非常に複雑な場合、コンパイラは同じベース名を持つ2つのヘッダの違いを見分けることができないかもしれません(例えばa/foo.hb/foo.h)、そのうちの1つの#pragma onceは抑制します両方とも。また、2つの異なる相対インクルード(例:#include "foo.h"#include "../a/foo.h"は同じファイルを参照しているため、#pragma onceは必要なときに冗長なインクルードを抑制できないこともわかりません)。

これは#ifndefガードでファイルを読み直さないようにするコンパイラの能力にも影響しますが、それは単なる最適化です。 #ifndefガードを使用すると、コンパイラは、すでに見たことのない確実でないファイルを安全に読み取ることができます。それが間違っているならば、それはちょうどいくらかの追加の仕事をしなければなりません。 2つのヘッダが同じガードマクロを定義しない限り、コードは期待どおりにコンパイルされます。また、2つのヘッダが同じガードマクロを定義している場合、プログラマはその一方を変更して変更することができます。

#pragma onceにはそのような安全策はありません - コンパイラがヘッダファイルの識別について間違っている場合、いずれにせよ、プログラムはコンパイルに失敗します。このバグに遭遇した場合、あなたの唯一の選択肢は#pragma onceの使用をやめるか、ヘッダの1つを改名することです。ヘッダの名前はAPI規約の一部なので、名前の変更はおそらく選択肢にならないでしょう。

(なぜこれが固定不可能であるかの簡単な説明は、UnixもWindowsファイルシステムAPIもそのようなメカニズムを提供していないということです。 2つの絶対パス名が同じファイルを参照しているかどうかを示すことを保証します。そのためにiノード番号を使用できるという印象を受けている場合は、申し訳ありません。

(歴史的な注意:私がそうする権限を持っていたのに12年前にGCCから#pragma once#importを切り取らなかった唯一の理由は、Appleのシステムヘッダがそれらに頼っていたことでした。 。)

(これがコメントスレッドで2回出てきたので:GCC開発者は#pragma onceをできるだけ信頼できるものにするためにかなりの努力をしました。 GCCバグレポート11569 を参照してください。 GCCのバージョンは、クロックスキューの影響を受けているビルドファームなど、もっともらしい状況ではまだ失敗する可能性があります。だれかがより良いしたとは思わないでしょう。)

100
zwol

#pragma onceが標準になるまで(これは現在の将来の標準の優先事項ではありません)、私はあなたがそれを使用し、警備員を使用することをお勧めします。

#ifndef BLAH_H
#define BLAH_H
#pragma once

// ...

#endif

その理由は:

  • #pragma onceは標準ではないので、コンパイラによっては機能を提供していない可能性があります。とは言っても、すべての主要なコンパイラがそれをサポートしています。コンパイラがそれを知らない場合は、少なくとも無視されます。
  • #pragma onceには標準的な動作はありませんので、その動作がすべてのコンパイラで同じになるとは限りません。ガードは、少なくともガードに必要なプリプロセッサ命令を実装するすべてのコンパイラに対して、基本的な前提が同じであることを保証します。
  • ほとんどのコンパイラでは、#pragma onceは(1 cppの)コンパイルを高速化します。これは、コンパイラがこの命令を含むファイルを再度開かないためです。そのため、ファイルに含めると、コンパイラによっては役に立ちます。ガードが検出されたときにg ++が同じ最適化を実行できると聞きましたが、確認する必要があります。

この2つを一緒に使うことで、それぞれのコンパイラの長所を生かすことができます。

さて、あなたがガードを生成するための自動スクリプトを持っていないのなら、単に#pragma onceを使うほうが便利かもしれません。それが移植可能なコードにとって何を意味するのかを知ってください。 (私はすぐにガードとプラグマを生成するためにVAssistXを使っています)

あなたは自分のコードを可搬性のある方法で考えるべきだが(未来はどうなるのかわからないので)、他のコンパイラでコンパイルすることを意図していないと本当に思うなら(例えば非常に特殊な組み込みハードウェア用のコード)次に、#pragma onceに関するコンパイラのドキュメントを調べて、実際に何をしているのかを確認してください。

33
Klaim

ソフトウェアテスターの観点から

#pragma onceは、ほとんどのコンパイラでサポートされている、インクルードガードよりも短く、エラーが発生しにくく、より速くコンパイルされると言う人もいます(これは真実ではありません)。

しかし、私はあなたが標準の#ifndefインクルードガードを使うことをまだ提案します。

なぜ#ifndefですか?

AB、およびCの各クラスがそれぞれ独自のファイル内に存在する、次のような考案されたクラス階層を考えてください。

#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

さて、あなたが自分のクラスのテストを書いていて、本当に複雑なクラスBのふるまいをシミュレートする必要があるとしましょう。これを行う1つの方法は、たとえば google mock を使用して モッククラス を作成し、それをディレクトリmocks/b.h内に配置することです。クラス名は変更されていませんが、別のディレクトリに格納されているだけです。しかし最も重要なことは、インクルードガードが元のファイルb.hとまったく同じ名前であることです。

モック/ 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

利点は何ですか?

このアプローチでは、元のクラスに触れたり、それについてBを教えたりせずに、クラスCの動作をモックすることができます。あなたがしなければならないのは、あなたのコンパイラのインクルードパスにディレクトリmocks/を置くことだけです。

なぜこれが#pragma onceでできないのですか?

#pragma onceを使用すると、クラスBを元のクラスと偽のバージョンの2回定義することから保護できないため、名前の衝突が発生します。

30
Konrad Kleine

サポートしていないコンパイラでこのコードを使用しないことに肯定的であれば(Windows/VS、GCC、Clangはdoサポートしているコンパイラの例です)、 #pragmaは、心配せずに必ず1回使用してください。

互換性のあるシステムでは移植性とコンパイルの高速化を図るために、両方を使用することもできます(下記の例を参照)。

#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_

...

#endif
22
Donnie DeBoer

#pragma onceガードと#ifndefガードとの間の想定されるパフォーマンスのトレードオフと正しさの引数に関する拡張議論に従事した後(私はいくつかの比較的最近の教化に基づいて#pragma onceの側をとっていましたそのために、コンパイラは既に含まれているファイルを再_#pragma onceしようとする必要がないため、#includeがより高速であるという理論を最終的にテストすることにしました。

テストのために、複雑な相互依存関係を持つ500個のヘッダーファイルを自動的に生成し、.cファイルですべてが#includesになるようにしました。テストは3つの方法で実行しました。1回は#ifndefで、1回は#pragma onceで、1回は両方で実行しました。私はかなり最新のシステム(XCodeにバンドルされているClangを使用し、内部SSDを使用してOSXを実行している2014 MacBook Pro)でテストを実行しました。

まず、テストコード:

#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;
}

そして今、私のさまざまなテストが実行されます:

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

ご覧のとおり、#pragma onceを含むバージョンは、#ifndefのみのプリプロセスよりも実際にわずかに高速でしたbut違いはごくわずかであり、実際にコードを構築してリンクするのにかかる時間。おそらく十分に大きなコードベースでは、実際にはビルド時間に数秒の差が生じる可能性がありますが、最新のコンパイラーは#ifndefガードを最適化できるため、OSには良好なディスクキャッシュがあり、ストレージ技術では、少なくとも今日の時代の典型的な開発者システムでは、パフォーマンスの議論は重要ではないと思われます。より古く、よりエキゾチックなビルド環境(例:ネットワーク共有でホストされているヘッダー、テープからのビルドなど)は、方程式を多少変更する可能性がありますが、そのような状況では、最初に脆弱性の少ないビルド環境を作成する方が便利です。

問題の事実は、#ifndefは標準の動作で標準化されているのに対し、#pragma onceはそうではなく、#ifndefも奇妙なファイルシステムと検索パスのコーナーケースを処理しますが、#pragma once特定の事柄に混乱し、プログラマーが制御できない不正な動作につながります。 #ifndefの主な問題は、(名前の衝突などで)ガードに悪い名前を選択するプログラマーであり、それでもAPIのコンシューマーが#undefを使用してそれらの悪い名前をオーバーライドすることは可能です。おそらく完璧な解決策ですが、可能ですが、コンパイラが誤って#pragma onceを間引きしている場合、#includeに頼ることはできません。

したがって、にもかかわらず#pragma onceは明らかに(わずかに)高速ですが、これが#ifndefガードで使用する理由であることに同意しません。

EDIT:@LightnessRacesInOrbitからのフィードバックのおかげで、ヘッダーファイルの数を増やし、プリプロセッサステップのみを実行するようにテストを変更し、コンパイルによって追加された短い時間を排除しました。およびリンクプロセス(以前は簡単でしたが、現在は存在しません)。予想どおり、差はほぼ同じです。

19
fluffy

私のコードはMSVCやGCC以外のものでコンパイルしなければならないことがあるので、私は一般的に#pragma onceを気にする必要はありません(組み込みシステム用のコンパイラは常に#pragmaを持っているとは限りません)。

それで私はとにかく#includeガードを使わなければなりません。いくつかの回答が示すように#pragma onceを使用することもできますが、それほど理由がないように思われ、それをサポートしていないコンパイラには不要な警告が表示されることがよくあります。

このプラグマがどのくらいの時間節約になるかはわかりません。一般的に、コンパイラはヘッダにガードマクロの外側にコメント以外の何もないことを認識しており、その場合は#pragma onceと同等の処理を実行する(つまり、ファイルを再び処理しない)ことをすでに聞いています。しかし、それが本当かどうか、コンパイラの場合がこの最適化を実行できるかどうかはわかりません。

どちらの場合でも、どこでも動作し、それ以上心配することのない#includeガードを使用するほうが簡単です。

15
Michael Burr

関連質問 to --- 私が答えた

#pragma onceには1つの欠点(非標準以外)があります。同じファイルが別の場所にある場合(これはビルドシステムがファイルをコピーするためです)、コンパイラはこれらが異なるファイルであると判断します。

誰かがこの質問についてつまずいていて、他の質問ではない場合に備えて、ここでも答えを追加しています。

10
Motti

私はあなたが最初にすべきことはこれが本当に違いを生むことになっているかどうかを確認することであると思います。まずパフォーマンスをテストする必要があります。 Googleでの検索の1つが失敗しました this

結果のページでは、コラムはほとんどわかりませんが、少なくともVC6まで、マイクロソフトは他のツールが使用していたインクルードガードの最適化を実装していませんでした。インクルードガードが内部にある場合は、インクルードガードが外部にある場合と比較して50倍の時間がかかりました(外部のインクルードガードは少なくとも#pragmaと同等です)。しかし、これによる影響を考えてみましょう。

提示された表によると、インクルードを開いてそれをチェックする時間は、#pragmaと同等の時間の50倍です。しかし、実際の所要時間は1999年当時のファイルあたり1マイクロ秒でした。

それで、単一のTUにいくつの重複ヘッダがあるでしょうか。これはあなたのスタイルによりますが、平均的なTUが100回重複していると言うと、1999年にはTUあたり100マイクロ秒かかる可能性があります。 HDDの改良により、これはおそらく今ではかなり低くなりますが、それでもプリコンパイルされたヘッダと正しい依存関係の追跡でプロジェクトのためのこれの総累積コストを追跡することはあなたのビルド時間のほんのわずかな部分です。

逆に、#pragma onceをサポートしていないコンパイラに移行した場合、#pragmaではなくガードを含めるようにソースベース全体を更新するのにどのくらい時間がかかるのかを考えてみてください。

MicrosoftがGCCや他のすべてのコンパイラが行うのと同じ方法でインクルードガード最適化を実装できなかった理由はありません(より新しいバージョンがこれを実装しているかどうかを実際に確認できる人はいますか?)。私見、#pragma onceはあなたの代わりのコンパイラの選択を制限すること以外ほとんど何もしません。

9
Richard Corden

#pragma onceを使用すると、#includeガードに到達するまでファイルを解析するのではなく、コンパイラはファイルが再度発生したときにそのファイルを完全にスキップできます。

そのため、セマンティクスは少し異なりますが、それらが使用されることを意図した方法で使用されている場合、それらは同一です。

最悪の場合(警告ではなく、未知のプラグマを実際のエラーとしてフラグ付けするコンパイラ)のように、両方を組み合わせることがおそらく最も安全な方法です。#プラグマ自体を削除する必要があります。

あなたのプラットフォームを「デスクトップの主流のコンパイラ」に限定するとき、あなたは安全に#includeガードを省略することができます、しかし私もそれについて不安を感じます。

OT:他にもビルドをスピードアップするためのヒントや経験があるのであれば、興味があるでしょう。

4
peterchen

#pragmaを1回使用してガードを一緒に入れたい人のために:MSVCを使用していないのであれば、#pragmaから1回はあまり最適化されません。

そして、あなたは "#pragma once"をヘッダに入れるべきではありません。

こちら は#pragma once usageについての例を含む詳細な議論です。

1
Deqing

Konrad Kleine による説明の上。

簡単な要約

  • 私たちが# pragma onceを使うとき、それがコンパイラの責任の大部分であり、それを2回以上含めることを許しません。つまり、ファイル内のコードスニペットについて言及した後は、それはあなたの責任ではなくなります。

さて、コンパイラはファイルの先頭でこのコードスニペットを探し、それがインクルードされるのをスキップします(すでにインクルードされている場合)。これは間違いなく(平均して巨大なシステムで)コンパイル時間を減らすでしょう。ただし、モック/テスト環境の場合、循環などの依存関係のためにテストケースの実装が困難になります。

  • さて、ヘッダに#ifndef XYZ_Hを使うとき、ヘッダの依存関係を維持するのは開発者の責任です。つまり、何らかの新しいヘッダファイルが原因で循環依存の可能性があるときはいつでも、コンパイラはコンパイル時に「undefined ..」エラーメッセージにフラグを立てるだけです。不適切なものが含まれています。

これは間違いなくコンパイル時間を増やします(修正して再実行する必要があるため)。また、 "XYZ_H"の定義済み状態に基づいてファイルをインクルードすることに基づいて動作しますが、すべての定義を取得できない場合は、まだ文句を言います。

したがって、このような状況を回避するには、次のように使用します。

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

すなわち両方の組み合わせ。

1
parasrish