web-dev-qa-db-ja.com

"静的定数"対 "#define"対 "列挙型"

以下のCのステートメントのうち、どれを使用するのが良いでしょうか。

static const int var = 5;

または

#define var 5

または

enum { var = 5 };
528
Vijay

一般的に言えば:

static const

それはスコープを尊重し、タイプセーフだからです。

私が見ることができた唯一の警告:あなたが変数をコマンドラインで定義される可能性がある場合。まだ選択肢があります。

#ifdef VAR // Very bad name, not long enough, too general, etc..
  static int const var = VAR;
#else
  static int const var = 5; // default value
#endif

可能な場合はいつでも、マクロ/省略記号の代わりに、タイプセーフな代替手段を使用してください。

もしあなたが本当にマクロを使う必要があるなら(例えば__FILE____LINE__が欲しいのなら)、そのマクロにはもっと慎重に名前をつけたほうがいいでしょう: 命名規則ではBoost プロジェクトの名前(ここではBOOST_)から始めますが、ライブラリを熟読している間、(一般的に)特定の領域(ライブラリ)の名前が続き、次に意味のある名前が続くことに気付くでしょう。

それは一般的に長い名前を作ります:)

267
Matthieu M.

それはあなたが価値を必要としているものによります。あなた(そして今までの他のみんな)は3番目の選択肢を省略しました:

  1. static const int var = 5;
  2. #define var 5
  3. enum { var = 5 };

名前の選択に関する問題を無視すると、

  • ポインタを渡す必要がある場合は、(1)を使用する必要があります。
  • (2)は明らかにオプションなので、ポインタを渡す必要はありません。
  • デバッガのシンボルテーブルに(1)と(3)の両方のシンボルがあるため、デバッグが容易になります。 (2)にはシンボルがないので、それが何であるか疑問に思うことが多いでしょう。
  • (1)グローバルスコープで配列の次元として使用することはできません。 (2)と(3)の両方が可能です。
  • (1)関数スコープで静的配列の次元として使用することはできません。 (2)と(3)の両方が可能です。
  • C99では、これらすべてをローカル配列に使用できます。技術的には、(1)を使用するとVLA(可変長配列)を使用することになりますが、 'var'で参照される次元はもちろんサイズ5に固定されます。
  • (1)switch文のような場所では使用できません。 (2)と(3)の両方が可能です。
  • (1)静的変数の初期化には使用できません。 (2)と(3)の両方が可能です。
  • (2)変更したくないコードはプリプロセッサで使用されているため変更できます。 (1)と(3)の両方がそのような予期しない副作用を持つことはありません。
  • (2)がプリプロセッサに設定されているかどうかを検出できます。 (1)も(3)もそれを許さない。

そのため、ほとんどの場合、選択肢よりも「列挙型」を選択してください。そうでなければ、最初と最後の箇条書きの点が支配要因になる可能性があります - そしてあなたが両方を同時に満たす必要があるなら、あなたはもっと慎重に考えなければなりません。

C++について質問しているのであれば、毎回option(1) - 静的定数 - を使用してください。

630

Cでは、具体的には? Cでは、正しい答えは次のとおりです。#define(または、適切な場合はenum)を使用します。

constオブジェクトのスコープと型付けのプロパティを持つことは有益ですが、実際には(C++とは対照的に)Cのconstオブジェクトは真の定数ではないため、ほとんどの場合実用的ではありません。

ですから、C言語では、定数をどのように使用する予定であるかによって選択を決定する必要があります。たとえば、const intオブジェクトをcaseラベルとして使用することはできません(マクロは機能します)。 const intオブジェクトをビットフィールド幅として使用することはできません(マクロは機能します)。 C89/90では、配列サイズを指定するためにconstオブジェクトを使用することはできません(マクロは動作します)。 C99でも、 - _ vla _ /以外の配列が必要なときは、constオブジェクトを使って配列サイズを指定することはできません。

これがあなたにとって重要であるならば、それはあなたの選択を決定します。ほとんどの場合、Cで#defineを使用する以外に選択肢はありません。そして、Cで正しい定数を生成する別の方法、enumを忘れないでください。

C++ではconstオブジェクトは真の定数なので、C++ではほとんどの場合constバリアントを使用することをお勧めします(ただし、C++では明示的なstaticは不要です)。

103
AnT

static const#defineの違いは、前者はメモリを使用し、後者はメモリをストレージとして使用しないことです。次に、#defineのアドレスを渡すことはできませんが、static constのアドレスを渡すことはできます。実際には、私たちがどのような状況下にあるかに応じて、これら2つの中から1つを選択する必要があります。どちらもさまざまな状況下で最高に機能します。一方が他方より優れているとは思わないでください... :-)

もしそうなら、 Dennis Ritchie だけで最高の1つを守っていただろう……笑…:-)

29
wrapperm

Cでは#defineはもっと人気があります。配列サイズを宣言するためにこれらの値を使うことができます。例えば:

#define MAXLEN 5

void foo(void) {
   int bar[MAXLEN];
}

私の知る限り、ANSI Cではこの文脈でstatic constsを使用することはできません。 C++では、このような場合はマクロを避けるべきです。あなたは書ける

const int maxlen = 5;

void foo() {
   int bar[maxlen];
}

内部リンケージは既にstaticによって暗黙のうちに暗示されているので(C++のみ)、constも除外します。

16
sellibitze

Cのconstのもう1つの欠点は、別のconstを初期化するときにその値を使用できないことです。

static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;

// initializer element is not constant, this does not work.
static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND 
                                     * NUMBER_OF_HANDS;

コンパイラでは定数と見なされないため、これでもconstでは機能しません。

static uint8_t const ARRAY_SIZE = 16;
static int8_t const lookup_table[ARRAY_SIZE] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!

そうでない場合は、constと入力してください。

13
Gauthier

うまくいけば、static constには多くの利点があります。通常のスコープの原則に従い、デバッガーで表示され、一般的に変数が従うルールに従います。

ただし、少なくとも元のC標準では、実際には定数ではありません。 #define var 5を使用する場合、int foo[var];を宣言として記述することはできますが、static const int var = 5;を使用して(コンパイラー拡張機能を除く)行うことはできません。C++ではそうではありません。 static constバージョンは#defineバージョンが使用できる場所であればどこでも使用できますが、これはC99にも当てはまると思います。

ただし、#define定数に小文字の名前を付けないでください。翻訳単位の終わりまで、その名前の使用可能性をオーバーライドします。マクロ定数は、事実上独自の名前空間に存在する必要があります。名前空間は、従来はすべて大文字で、おそらく接頭辞が付いています。

10
David Thornley

1つの違いを示すためにクイックテストプログラムを書きました。

#include <stdio.h>

enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};

#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32

int main(int argc, char *argv[]) {

   printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);

   return(0);
}

これはこれらのエラーおよび警告に準拠しています。

main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
      ^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
      ^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
        ^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
        ^

Defineが警告を出すと、enumはエラーを出すことに注意してください。

6
Michael Potter

#define var 5のようなものがある場合、mystruct.varはあなたに問題を引き起こすでしょう。

例えば、

struct mystruct {
    int var;
};

#define var 5

int main() {
    struct mystruct foo;
    foo.var = 1;
    return 0;
}

プリプロセッサがそれを置き換えて、コードはコンパイルされません。このため、従来のコーディングスタイルでは、競合を避けるためにすべての定数#definesに大文字を使用することが推奨されています。

#defineの代わりにconstを使用することが常に好ましいです。これは、constはコンパイラによって、#defineはプリプロセッサによって処理されるためです。 #define自体がコードの一部ではないようです(大まかに言って)。

例:

#define PI 3.1416

シンボル名PIは、コンパイラからは見られないかもしれません。ソースコードがコンパイラに到達する前に、プリプロセッサによって削除される可能性があります。その結果、名前PIがシンボルテーブルに入力されない場合があります。エラーメッセージはPIではなく3.1416を参照する可能性があるため、コンパイル中に定数の使用を含むエラーが発生した場合、これは混乱を招く可能性があります。あなたが書いていないヘッダファイルでPIが定義されていたら、その3.1416がどこから来たのか分からないでしょう。

この問題は、シンボリックデバッガでも発生する可能性があります。これも、プログラミングしている名前がシンボルテーブルに表示されていない可能性があるためです。

溶液:

const double PI = 3.1416; //or static const...
5
suren

定義

const int const_value = 5;

常に定数値を定義するわけではありません。いくつかのコンパイラ(例えば tcc 0.9.26 )は単に "const_value"という名前で識別されるメモリを割り当てます。識別子 "const_value"を使用して、このメモリを変更することはできません。しかし、それでも別の識別子を使ってメモリを変更することができます。

const int const_value = 5;
int *mutable_value = (int*) &const_value;
*mutable_value = 3;
printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.

これは定義を意味します

#define CONST_VALUE 5

決して変更できない定数値を定義する唯一の方法です。

4
user2229691

ちなみに、適切なスコープを提供しますが、 "本当の"定数のように振る舞う#defineの代替は "enum"です。例えば:

enum {number_ten = 10;}

多くの場合、列挙型を定義してそれらの型の変数を作成すると便利です。それが行われると、デバッガはその列挙名に従って変数を表示できる可能性があります。

ただし、それを行う際の重要な注意点の1つは、C++では、列挙型は整数との互換性が限られていることです。たとえば、デフォルトでは、それらに対して算術演算を実行することはできません。私はそれがenumにとっては奇妙なデフォルトの振る舞いだと思います。 C++とCの互換性を一般的に望んでいることを考えると、 "厳密なenum"型を持つのはいいことでしたが、 "enum"型のデフォルトの振る舞いは整数と交換可能であるべきです。

3
supercat

「常に最高」の答えがあるとは思わないでください、しかし、マティウが言ったように

static const

タイプセーフです。 #defineの最大の利点は、 Visual Studioでのデバッグ時です あなたは変数を見ることができません。シンボルが見つからないというエラーが発生します。

3
Afcrowe

問題は整数に関するものでしたが、定数構造や文字列が必要な場合は#defineとenumは役に立ちません。これらは通常、ポインタとして関数に渡されます。 (文字列の場合は必須、構造体の場合ははるかに効率的です)

整数に関して言えば、メモリが非常に限られた組み込み環境では、定数がどこに格納されているのか、そしてそれへのアクセスがどのようにコンパイルされるのかについて心配する必要があるかもしれません。コンパイラは実行時に2つのconstを追加しますが、コンパイル時に2つの#defineを追加します。 #define定数は、1つ以上のMOV [即値]命令に変換できます。これは、定数がプログラムメモリに効果的に格納されることを意味します。定数定数はデータメモリの.constセクションに格納されます。ハーバードアーキテクチャを採用したシステムでは、パフォーマンスとメモリ使用量に差がある可能性がありますが、それらは小さい可能性があります。それらは内部ループのハードコア最適化にとって重要かもしれません。

2
Adam Haun

単純な違い

前処理時に、定数はその値に置き換えられます。したがって、間接参照演算子を定義に適用することはできませんが、間接参照演算子を変数に適用することはできます。

ご想像のとおり、defineはstatic constよりも高速です。

たとえば、

#define mymax 100

printf("address of constant is %p",&mymax);はできません。

しかし

const int mymax_var=100

あなたはprintf("address of constant is %p",&mymax_var);をすることができます。

より明確にするために、定義は前処理段階でその値によって置き換えられるので、プログラムには変数が格納されていません。定義が使用されたプログラムのテキストセグメントからのコードだけがあります。

しかし、静的constには、どこかに割り当てられた変数があります。 gccの場合、静的constはプログラムのテキストセグメントに割り当てられます。

上記では、参照演算子について説明したかったので、間接参照を参照に置き換えます。

1
mihaitzateo

私が正しいかどうかはわかりませんが、私の意見では、#defined値を呼び出すほうが、他の通常宣言されている変数(またはconst値)を呼び出すよりもはるかに高速です。これは、プログラムが実行されていて、通常宣言されている変数を使用する必要があるときに、その変数を取得するためにメモリ内の正確な場所にジャンプする必要があるためです。

反対に、#defined値を使用する場合、プログラムは割り当てられたメモリにジャンプする必要はなく、単に値を取得します。 #define myValue 7とプログラムがmyValueを呼び出している場合は、7を呼び出したときとまったく同じように動作します。

0
pajczur

私たちはMBF16X上で生成されたアセンブラコードを調べました。

そのため、型チェックにはconst intが優先されますが、#defineは古いスタイルです。たぶんそれはコンパイラ特有のものです。それで、あなたの生成されたアセンブラコードをチェックしてください。

0
guest