web-dev-qa-db-ja.com

#defineは業界標準で禁止されていますか?

私はコンピューターサイエンスの1年生で、教授は#define#if#ifdef#else、およびその他のいくつかのプリプロセッサディレクティブと共に業界標準で禁止されていると言いました。彼は予期せぬ振る舞いのために「禁止」という言葉を使いました。

これは正確ですか?もしそうなら、なぜですか?

実際、これらの指令の使用を禁止する規格はありますか?

68
psrag anvesh

最初に聞いたことがあります。

いいえ。 #defineなどが広く使用されています。時々あまりにも広く使用されますが、間違いなく使用されます。 C標準でマクロの使用が義務付けられている場所がありますが、それらを簡単に回避することはできません。たとえば、§7.5Errors <errno.h>は次のとおりです。

マクロは

     EDOM
     EILSEQ
     ERANGE

これは、int型の整数定数式、明確な正の値に展開され、#if前処理ディレクティブでの使用に適しています。 …

これを考えると、すべての業界標準がCプリプロセッサマクロディレクティブの使用を禁止しているわけではないことは明らかです。ただし、Cプリプロセッサの使用を完全に禁止するものはありませんが、Cプリプロセッサの使用に制限を規定するさまざまな組織からの「ベストプラクティス」または「コーディングガイドライン」の標準があります。多くの場合、これらの標準は、安全が重要な領域で働く人々を対象としています。

MISRA C (2012)標準を確認できる1つの標準。それは物事を禁止する傾向がありますが、それでも#defineなどが必要であることを認識しています(セクション8.20、規則20.1〜20.14はCプリプロセッサをカバーしています)。

NASA GSFC(Goddard Space Flight Center) Cコーディング標準 単に言う:

マクロは、必要な場合にのみ使用してください。マクロが過度に使用されると、コードが標準Cのように読み取ったり動作したりしなくなるため、コードの読み取りと保守が難しくなります。

その紹介文の後の議論は、関数マクロの許容可能な使用法を示しています。

CERT C Coding Standard には、プリプロセッサの使用に関する多くのガイドラインがあり、プリプロセッサの使用を最小限に抑える必要があるが、その使用を禁止しないことを意味します。

Stroustrupは、C++でプリプロセッサを無関係にしたいと考えていますが、まだ実現していません。 ピーターJSF AV C++コーディング標準Joint Strike Fighter、Airなどの一部のC++標準Vehicle)2005年頃から、Cプリプロセッサの最小限の使用が要求されています。基本的に、JSF AV C++ルールは、単一ヘッダーの複数の包含を防ぐ#includeおよび#ifndef XYZ_H/#define XYZ_H /…/ #endifダンスに制限しています。 C++には、Cでは使用できないオプションがいくつかあります。特に、Cで使用できない場所で使用できる型付き定数のサポートが向上しています。そこの問題についての議論は static const vs #define vs enum も参照してください。

プリプロセッサの使用を最小限に抑えることをお勧めします—少なくとも使用されている分だけ悪用されることがよくあります(詳細については、 Boostpreprocessor 'library'を参照してください) Cプリプロセッサをどこまで使用できるか)。

概要

プリプロセッサはCの不可欠な部分であり、#defineおよび#ifなどを完全に回避することはできません。質問の教授による声明は一般に有効ではありません:#define#if#ifdef#elseと共に業界標準で禁止されています、および他のいくつかのマクロはせいぜいオーバーステートメントですが、特定の業界標準への明示的な参照でサポート可能かもしれません(ただし、問題の標準にはISO/IEC 9899:2011 — C標準は含まれません) 。


David Hammen には 提供された情報 があり、特定のCコーディング標準— JPL Cコーディング標準 に注意してください。 Cでの使用(Cプリプロセッサの使用の制限(および動的メモリ割り当ての使用の制限、再帰の禁止)を読んで理由を確認し、それらの理由が自分に関連するかどうかを判断します)。

141

いいえ、マクロの使用は禁止されていません。

実際、ヘッダーファイルでの#includeガードの使用は、一般的な手法の1つであり、多くの場合、必須であり、受け入れられているコーディングガイドラインによって推奨されています。一部の人々は#pragma onceがそれに代わるものであると主張していますが、問題は#pragma once-定義により、プラグマはコンパイラ固有の拡張機能の標準によって提供されるフックであるため、多数のコンパイラーでサポートされている場合。

そうは言っても、マクロが導入する問題(スコープを尊重しないなど)のため、#includeガード以外のマクロの使用を積極的に阻止する多くの業界ガイドラインと推奨プラクティスがあります。 C++開発では、マクロの使用はC開発よりもさらに強く嫌われています。

正当化を文書化するなどして、合法的に使用することは依然として可能であるため、何かの使用を推奨しないことは禁止することと同じではありません。

33
Peter

一部のコーディング標準では、_#define_を使用して、引数をとる関数のようなマクロを作成することを推奨しないか、禁止する場合があります。

_#define SQR(x) ((x)*(x))
_

a)そのようなマクロはタイプセーフではなく、b)誰かが必然的にSQR(x++)を書くからです。これは悪いjujuです。

一部の標準では、条件付きコンパイルに_#ifdef_ sを使用しないか、禁止する場合があります。たとえば、次のコードでは、条件付きコンパイルを使用して_size_t_値を適切に出力します。 C99 以降では、_%zu_変換指定子を使用します。 C89 以前では、_%lu_を使用し、値を_unsigned long_にキャストします。

_#if __STDC_VERSION__ >= 199901L
#  define SIZE_T_CAST
#  define SIZE_T_FMT "%zu"
#else
#  define SIZE_T_CAST (unsigned long)
#  define SIZE_T_FMT "%lu"
#endif
...
printf( "sizeof foo = " SIZE_T_FMT "\n", SIZE_T_CAST sizeof foo );
_

いくつかの標準mayは、これを行う代わりに、モジュールを2回、C89以前に1回、C99以降に1回実装することを義務付けています。

_/* C89 version */
printf( "sizeof foo = %lu\n", (unsigned long) sizeof foo );

/* C99 version */
printf( "sizeof foo = %zu\n", sizeof foo );
_

そして、 Make (または Ant 、または使用しているビルドツール)が正しいバージョンのコンパイルとリンクを処理するようにします。この例ではばかげていますが、shouldが持っている_#ifdef_ sの追跡不可能なネズミの巣であるコードを見てきました条件付きコードは個別のファイルに分解されました。

ただし、プリプロセッサステートメントの使用をbanedで完全に禁止している会社または業界グループについては知りません。

30
John Bode

マクロは「禁止」できません。ステートメントはナンセンスです。文字通り。

たとえば、セクション7.5 Errors <errno.h>of C Standardrequiresマクロの使用:

1ヘッダー<errno.h>は、すべてエラー状態の報告に関連するいくつかのマクロを定義します。

2マクロは

EDOM
EILSEQ
ERANGE

タイプint、明確な正の値を持つ整数定数式に展開され、#ifプリプロセスディレクティブでの使用に適しています。そして

errno

これは、タイプintとスレッドローカルストレージ期間を持つ変更可能な左辺値に展開され、その値はいくつかのライブラリ関数によって正のエラー番号に設定されます。実際のオブジェクトにアクセスするためにマクロ定義が抑制されている場合、またはプログラムがerrnoという名前の識別子を定義している場合、動作は未定義です。

したがって、マクロはCのrequired部分であるだけでなく、マクロを使用しないと未定義の動作が発生する場合があります。

16
Andrew Henle

いいえ、_#define_は禁止されていません。ただし、_#define_の誤用は嫌われるかもしれません。

たとえば、使用することがあります

_#define DEBUG_

コード内で、後でデバッグ目的でのみ_#ifdef DEBUG_を使用して条件付きコンパイルのためにコードの一部を指定できるようにします。彼の正しい考えの誰かがこのようなことを禁止したいとは思わない。 _#define_を使用して定義されたマクロは、プラットフォーム固有のコードのコンパイルを有効/無効にするために、ポータブルプログラムでも広く使用されています。

ただし、次のようなものを使用している場合

_#define PI 3.141592653589793_

先生はPIを適切な型の定数として宣言する方がはるかに良いことを正しく指摘するかもしれません。

_const double PI = 3.141592653589793;_

PIが使用されているときにコンパイラが型チェックを行うことができるためです。

同様に(上記のJohn Bodeが述べたように)、特にテンプレートを使用できるC++では、関数のようなマクロの使用が承認されない場合があります。の代わりに

#define SQ(X) ((X)*(X))

使用を検討する

double SQ(double X) { return X * X; }

または、C++では、さらに良いことに、

template <typename T>T SQ(T X) { return X * X; }

この場合も、プリプロセッサの代わりに言語の機能を使用することで、コンパイラに型チェックを許可し、(おそらく)より良いコードを生成するという考え方です。

十分なコーディング経験があれば、_#define_を使用するのが適切である時期を正確に知ることができます。それまでは、教師が特定のルールとコーディング標準を課すことは良い考えだと思いますが、できれば彼ら自身が理由を知り、説明できるようにするべきです。 _#define_の全面禁止は無意味です。

15
Viktor Toth

それは完全に間違っています。マクロはCで頻繁に使用されます。初心者はしばしばマクロを不適切に使用しますが、それは業界からマクロを禁止する理由ではありません。古典的な悪い使い方は#define succesor(n) n + 1です。 2 * successor(9)が20になると予想する場合、その式は2 * 9 + 1すなわち20ではなく19。期待される結果を得るには括弧を使用します。

12
mikedu95

いいえ。禁止されていません。そして真実は、それなしでは重要なマルチプラットフォームコードを実行することは不可能です。

11
luis.espinal

教授が間違っていたり、何かを聞き間違えたりしていません。

#defineはプリプロセッサマクロであり、条件付きコンパイルといくつかの規則のためにプリプロセッサマクロが必要です。これらは単にC言語で構築されたものではありません。たとえば、最近のC標準、つまりC99では、ブール値のサポートが追加されていました。しかし、言語では「ネイティブ」ではなく、プリプロセッサ#defines。 stdbool.hへのこの参照 を参照してください

8
Superlokkus

これまでのすべての回答に反して、高信頼性コンピューティングではプリプロセッサディレクティブの使用がしばしば禁止されています。これには2つの例外があり、そのような組織ではその使用が義務付けられています。これらは#includeディレクティブ、およびヘッダーファイルでのインクルードガードの使用。これらの種類の禁止は、CではなくC++の可能性が高いです。

次に例を示します。16.1.1インクルードガードの実装、およびインクルードガード付きのヘッダーファイルのインクルードにのみプリプロセッサを使用します

別の例、今度はC++ではなくCの場合:Cプログラミング言語のJPL制度コーディング標準。このCコーディング標準は、プリプロセッサの使用を完全に禁止するほどではありませんが、近づいています。具体的には、

ルール20(プリプロセッサの使用)Cプリプロセッサshallの使用は、ファイルの包含と単純なマクロに制限されます。 [10の力の規則8]。


私はそれらの標準を容認も否定もしていません。しかし、それらが存在しないと言うのはばかげています。

6
David Hammen

マクロはGNU land Cでかなり頻繁に使用されます。条件付きプリプロセッサコマンドがないと、同じソースファイルの複数のインクルードを適切に処理する方法がありません。 。

おそらくあなたのクラスは実際にはC++上にあり、多くの人がそうしなかったにもかかわらず、Cとは異なる言語であるため区別する必要があり、そこではマクロを話すことはできません。または、教授はクラスでそれらを禁止することを意味したのかもしれません。とにかくSOコミュニティは、彼が話している標準を聞くことに興味があると確信しています。なぜなら、すべてのC標準がマクロの使用をサポートしているからです。

6
Daniel Farrell

CコードをC++コードと相互運用する場合は、extern "C"名前空間で、関数宣言などの外部から見えるシンボルを宣言する必要があります。多くの場合、これは条件付きコンパイルを使用して行われます。

#ifdef __cplusplus
extern "C" {
#endif

/* C header file body */

#ifdef __cplusplus
}
#endif
2
jxh

ヘッダーファイルを見ると、次のようなものが表示されます。

#ifndef _FILE_NAME_H
#define _FILE_NAME_H
//Exported functions, strucs, define, ect. go here
#endif /*_FILE_NAME_H */

これらの定義は許可されるだけでなく、ヘッダーファイルがファイルで参照されるたびに個別に含まれるため、本質的に重要です。これは、定義なしでガード間のすべてを複数回再定義することを意味します。最良のケースはコンパイルに失敗し、最悪のケースではコードが思うように動作しない理由を後で頭に残します。

また、コンパイラは ここではgccを使用 のようにdefineを使用します。これにより、非常に役立つコンパイラのバージョンなどをテストできます。現在、avr-gccでコンパイルする必要のあるプロジェクトに取り組んでいますが、コードを実行するテスト環境もあります。 avr固有のファイルとレジスタがテストコードの実行を妨げないようにするには、次のようにします。

#ifdef __AVR__
//avr specific code here
#endif

これを製品コードで使用すると、補完的なテストコードはavr-gccを使用せずにコンパイルでき、上記のコードはavr-gccを使用してのみコンパイルされます。

1
Dom

#defineについて言及したばかりの場合、列挙に使用することを示唆しているのではないかと思います。同じ数値を2回割り当てるなどの愚かなエラーを避けるには、enumを使用する方が良いでしょう。

この状況でも、たとえば、他のシステムと交換した数値に依存しており、定数を追加/削除しても実際の値が同じままである必要がある場合など、enumよりも#definesを使用した方がよい場合があることに注意してください互換性)。

ただし、#if#ifdefなどを使用しないように追加するのも奇妙です。もちろん、おそらく乱用されるべきではありませんが、実際にはそれらを使用する理由はたくさんあります。

彼が意味するかもしれないことは、(適切な場合)、ソースの動作をハードコードすることはできません(異なる動作を得るには再コンパイルが必要になります)が、代わりに何らかの形式のランタイム設定を代わりに使用する必要があります。

それが理にかなっていると私が考えることができる唯一の解釈です。

1
jcaron