#pragmaが一度自動的に想定されないのはなぜですか?


81

ファイルを1回だけ含めるようにコンパイラーに指示する意味は何ですか?デフォルトでは意味がありませんか?1つのファイルを複数回含める理由はありますか?なぜそれを仮定しないのですか?それは特定のハードウェアと関係がありますか?


24
1つのファイルを複数回含める理由はありますか?=>可能性があります。ファイルには条件付きコンパイルが含ま#ifdefsれている場合があります。だからあなたは言うかもしれないが#define MODE_ONE 1、その後#include "has-modes.h"、その後#undef MODE_ONE#define MODE_TWO 1#include "has-modes.h"再び。プリプロセッサはこれらの種類のものにとらわれず、場合によっては意味をなすことがあります。
HostileForkは、SEを信頼しないと言っています2018

66
デフォルトとしては理にかなっています。Cプログラマーがまだ馬に乗って銃を持ち、16KBのメモリを持っていたときに彼らが選んだものではありません
HansPassant18年

11
同じソースファイルに、の<assert.h>定義が異なる複数回含めることができNDEBUGます。
ピートベッカー

3
それ#pragma once自体に関しては、正しく機能しないハードウェア環境(通常はネットワークドライブと同じヘッダーへの複数のパスの可能性がある)があります。
ピートベッカー

12
あなたが#pragma once仮定した場合、そのデフォルトを打ち消す方法は何ですか? #pragma many?何人のコンパイラがそのようなものを実装しましたか?
ジョナサンレフラー2018年

回答:


85

ここには複数の関連する質問があります:

  • なぜ#pragma once自動的に強制されないのですか?
    ファイルを複数回含めたい場合があるからです。

  • ファイルを複数回インクルードしたいのはなぜですか?
    他の回答にはいくつかの理由があります(Boost.Preprocessor、X-Macros、データファイルを含む)。「コードの重複を避ける」という特定の例を追加したいと思います。OpenFOAM#includeは、関数内の断片を一般的な概念にするスタイルを推奨しています。たとえば、この説明を参照してください。

  • わかりました、しかしなぜそれがオプトアウトのデフォルトではないのですか?
    それは実際には規格で指定されていないからです。#pragmaは、定義上、実装固有の拡張機能です。

  • なぜ#pragma onceまだ標準化された機能になっていないのですか(広くサポートされているため)?
    プラットフォームにとらわれない方法で「同じファイル」を特定することは、実際には驚くほど難しいためです。詳細については、この回答を参照してください



4
特に、失敗したがインクルードガードが機能した場合については、この例参照してくださいpragma once。プロジェクト内で同じファイルが複数回発生することがあるため、場所によるファイルの識別も機能しません(たとえば、ヘッダーにヘッダーのみのライブラリが含まれ、そのコピーをチェックアウトする2つのサブモジュールがある場合)
MM

6
すべてのプラグマが実装固有の拡張機能であるとは限りません。例:#pragma STDC家族。しかし、それらはすべて、実装で定義された動作を制御します。
ルスラン

4
@ user4581301この回答は、プラグマの問題を一度誇張しており、警備員が含まれていることによる問題は考慮していません。どちらの場合も、ある程度の規律が必要です。インクルードガードを使用する場合は、他のファイルで使用されない名前を使用する必要があります(これはファイルコピーの変更後に発生します)。一度プラグマを使用すると、ファイルの一意の適切な場所を決定する必要があります。これは結局のところ良いことです。
オリブ2018年

3
@Mehrdad:コンパイラがソースファイルに書き込むことを真剣に提案していますか?コンパイラがを検出した場合、ファイル全体を読み取るまで#ifndef XX、対応するものの後に何かがあるかどうかを知ることはできません#endif。最も外側#ifndefがファイル全体を囲んでいるかどうかを追跡し、チェックするマクロを記録するコンパイラは、ファイルの再スキャンを回避できる可能性がありますが、現在のポイントの後に重要なことは何もないと言うディレクティブは、コンパイラに依存するよりも優れているように見えます。そのようなことを覚えておいてください。
スーパーキャット2018年

38

関数内のように、グローバルスコープだけでなく、ファイル内の#include どこでも使用できます(必要に応じて複数回)。確かに、醜く、良いスタイルではありませんが、可能であり、時には賢明です(含めるファイルによって異なります)。#include一度だけだったら、それはうまくいきません。#include結局のところ、ダムテキストの置換(カットアンドペースト)を行うだけであり、インクルードするすべてがヘッダーファイルである必要はありません。たとえば#include、を初期化するための生データを含む自動生成データを含むファイルがありますstd::vector。お気に入り

std::vector<int> data = {
#include "my_generated_data.txt"
}

また、「my_generated_data.txt」は、コンパイル中にビルドシステムによって生成されるものにします。

または多分私は怠惰/愚か/愚かでこれをファイルに入れます(非常に不自然な例):

const noexcept;

そして私はします

class foo {
    void f1()
    #include "stupid.file"
    int f2(int)
    #include "stupid.file"
};

もう1つの少し不自然な例は、多くの関数が名前空間で大量の型を使用する必要があるソースファイルですがusing namespace foo;、グローバルな名前空間を他の多くのもので汚してしまうため、グローバルに言いたくないでしょう。あなたはしたくない。したがって、以下を含むファイル「foo」を作成します

using std::vector;
using std::array;
using std::rotate;
... You get the idea ...

そして、あなたはあなたのソースファイルでこれを行います

void f1() {
#include "foo" // needs "stuff"
}

void f2() {
    // Doesn't need "stuff"
}

void f3() {
#include "foo" // also needs "stuff"
}

注:私はこのようなことをすることを推奨していません。しかし、それは可能であり、いくつかのコードベースで行われており、なぜそれが許可されるべきではないのかわかりません。それに用途があります。

また、含めるファイルが特定のマクロ(#defines)の値に応じて異なる動作をする可能性もあります。したがって、最初に値を変更した後、ファイルを複数の場所に含めて、ソースファイルのさまざまな部分でさまざまな動作を取得することができます。


1
これは、すべてのヘッダーが一度プラグマであった場合でも機能します。生成されたデータを複数回含めなかった場合に限ります。
PSkocik 2018年

2
@PSkocikしかし、多分私はそれを複数回含める必要があります。なぜ私はできないのですか?
JesperJuhl18年

2
@JesperJuhlそれがポイントです。これまでに2回以上含める必要ありません。現在、オプションがありますが、代替案があったとしても、それほど悪くはありません。
ジョニーキャッシュ

9
@PSkocikインクルードさ#defineれたファイルの動作を変更する各インクルードの前にsの値を変更した場合、ソースファイルのさまざまな部分でそれらのさまざまな動作を取得するために、複数回インクルードする必要があります。
JesperJuhl18年

27

複数回含めることは、たとえばXマクロ手法で使用できます。

data.inc:

X(ONE)
X(TWO)
X(THREE)

use_data_inc_twice.c

enum data_e { 
#define X(V) V,
   #include "data.inc"
#undef X
};
char const* data_e__strings[]={
#define X(V) [V]=#V,
   #include "data.inc"
#undef X
};

他の用途については知りません。


それは非常に複雑に聞こえます。そもそもこれらの定義をファイルに含めない理由はありますか?
ジョニーキャッシュ

2
@JohnnyCache:この例は、Xマクロの動作を簡略化したものです。リンクをお読みください。表形式のデータを複雑に操作する場合に非常に便利です。Xマクロの重要な使用法では、単に「それらの定義をファイルに含める」ことはできません。
ニコルボーラス2018年

2
@ Johnny-はい-一貫性を確保することが1つの理由です(要素が数十個しかない場合は手作業で行うのは難しく、数百個も気にしないでください)。
Toby Speight 2018年

@TobySpeightええと、他の場所に何千もの書き込みを避けるために、1行のコードを節約できると思います。理にかなっています。
ジョニーキャッシュ

1
重複を避けるため。特にファイルが大きい場合。確かに、Xマクロリストを含む大きなマクロを使用することもできますが、プロジェクトでこれを使用できるため、#pragma once動作を義務付けることは重大な変更になります。
PSkocik 2018年

21

あなたは、言語に存在する「#include」機能の目的が、プログラムを複数のコンパイル単位に分解するためのサポートを提供することであるという仮定の下で操作しているようです。それは正しくありません。

それはその役割を果たすことができますが、それは意図された目的ではありませんでした。Cは元々、PDP- 11Macro-11よりもわずかに高水準の言語として開発されました。、Unixを再実装するためにアセンブリ。Macro-11の機能であるため、マクロプリプロセッサが搭載されていました。新しいCコンパイラに移植していた既存のUnixが利用していたMacro-11の機能であったため、別のファイルのマクロをテキストで含めることができました。

ここで、「#include」は、(おそらく)ちょっとしたハックとして、コードをコンパイル単位に分割するのに役立つことがわかりました。ただし、このハッキングが存在したという事実は、それがCで行われる方法になったということを意味しました。方法が存在したという事実は、この機能を提供するために新しいメソッドを作成する必要がないことを意味しました。-包含)が作成されました。すでにCに含まれていたため、Cの残りの構文とイディオムのほとんどとともにC ++にコピーされました。

この45年前のプリプロセッサハックをようやく省くことができるように C ++に適切なモジュールシステムを提供するという提案があります。しかし、これがどれほど差し迫っているのかはわかりません。私はそれが10年以上の間作業中であると聞いています。


5
いつものように、CとC ++を理解するには、それらの履歴を理解する必要があります。
ジャックエイドリー2018年

モジュールが2月に着陸すると予想するの合理的です。
デイビスヘリング

7
@ DavisHerring-はい、でもどの2月ですか?
TED

10

いいえ、これは、たとえば図書館の執筆者が利用できるオプションを大幅に妨げることになります。たとえば、Boost.Preprocessorを使用すると、プリプロセッサループを使用できます。これらを実現する唯一の方法は、同じファイルを複数インクルードすることです。

そして、Boost.Preprocessorは、多くの非常に便利なライブラリの構成要素です。


1
それは妨げないでしょうどんなことをします。OPは、変更できない動作ではなく、デフォルトの動作について質問しました。デフォルトを変更し、代わり#pragma reentrantにこれらの行に沿ってプリプロセッサフ​​ラグなどを提供することは完全に賢明です。後知恵は20/20です。
Konrad Rudolph

それは人々に彼らのライブラリと依存関係を更新することを強制するという意味でそれを妨げるでしょう、@ KonradRudolph。常に問題になるとは限りませんが、一部のレガシープログラムで問題が発生する可能性があります。理想的には、この問題やその他の潜在的な問題を軽減するために、デフォルトがonceまたはであるかどうかを指定するコマンドラインスイッチもありreentrantます。
ジャスティンタイム-モニカを復活させる2018年

1
@JustinTime私のコメントによると、下位互換性のある(したがって実行可能な)変更ではないことは明らかです。ただし、問題は、最初はそのように設計された理由であり、変更されていない理由ではありません。そして、その答えは、明らかに、元の設計は広範囲にわたる結果を伴う大きな間違いであったということです。
Konrad Rudolph

8

私が主に取り組んでいる製品のファームウェアでは、関数とグローバル/静的変数をメモリ内のどこに割り当てるかを指定できる必要があります。リアルタイム処理は、プロセッサが直接高速にアクセスできるように、チップ上のL1メモリに常駐する必要があります。重要度の低い処理は、チップ上のL2メモリに入れることができます。また、特に迅速に処理する必要のないものはすべて、外部DDRに存在し、キャッシュを通過できます。これは、少し遅いかどうかは問題ではないためです。

物事が進む場所を割り当てる#pragmaは、長くて重要な行です。間違えるのは簡単でしょう。それを間違えると、コード/データがサイレントにデフォルト(DDR)メモリに入れられ、その影響は見やすい理由もなく閉ループ制御が機能しなくなる可能性があります。

そのため、そのプラグマだけを含むインクルードファイルを使用します。私のコードは次のようになりました。

ヘッダーファイル...

#ifndef HEADERFILE_H
#define HEADERFILE_H

#include "set_fast_storage.h"

/* Declare variables */

#include "set_slow_storage.h"

/* Declare functions for initialisation on startup */

#include "set_fast_storage.h"

/* Declare functions for real-time processing */

#include "set_storage_default.h"

#endif

そしてソース...

#include "headerfile.h"

#include "set_fast_storage.h"

/* Define variables */

#include "set_slow_storage.h"

/* Define functions for initialisation on startup */

#include "set_fast_storage.h"

/* Define functions for real-time processing */

ヘッダーだけでも、同じファイルが複数含まれていることに気付くでしょう。ここで何かを間違って入力すると、コンパイラはインクルードファイル「set_fat_storage.h」が見つからないことを通知し、簡単に修正できます。

したがって、あなたの質問に答えると、これは複数の包含が必要な場合の実際の実際的な例です。


3
あなたのユースケースは、_Pragmaディレクティブの動機付けの例だと思います。同じプラグマを通常のマクロから展開できるようになりました。したがって、複数回含める必要はありません。
StoryTeller-Unslander Monica 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.