web-dev-qa-db-ja.com

なぜ2つの異なる列挙列挙定数が同じ整数値を持つことができるのですか?

次のように列挙型の平日を定義すると、

enum weekday {
    MON,
    TUE,
    WED,
    THU,
    FRI,
};

次に、MONはデフォルトで内部的に0、TUEが1、WEDが2に等しくなります...

しかし、このように定義すると:

enum weekday {
    MON,
    TUE = 0,
    WED,
    THU,
    FRI,
};

次に、MONTUEの両方が0の値を取得します。

システムは内部でMONとTUEをどのように区別しますか?つまり、次のように宣言すると:

enum weekday today = 0;

では、今日はMONまたはTUEですか?または、哲学的に言えば、両方ですか?

43
Xu Hong

C列挙型は「実際に」整数です。たまたまそのように実装されているためだけでなく、標準defines列挙型が整数値を持つためです。したがって、todayの値は「本当に」0です。発生したのは、値0に対して2つの異なる名前を作成したことだけです。

「今日は月か火か」に対する答えは「はい」だと思う;-)

列挙型が同じ値に対して複数の名前を持っていると便利な場合があるため、言語はあなたを止めません。例えば:

enum compression_method {
    COMP_NONE = 0,
    COMP_LOW = 1,
    COMP_HIGH = 2,
    COMP_BEST = 2,
    COMP_FASTEST = 0,
};
66
Steve Jessop

2つの異なる列挙定数が同じ整数値を持つことができるのはなぜですか?

N1265 C99標準ドラフト 6.7.2.2/3「列挙指定子」で明示的に許可されているため:

_=_で列挙子を使用すると、同じ列挙内の他の値と重複する値を持つ列挙定数が生成される場合があります。

システムは、内部でMONとTUEをどのように区別しますか?

コンパイル時定数(6.6/6 "定数式")であるため、不可能だと思います。結果として、彼らは:

  • コンパイル後に異なるように変更することはできません

  • それらを区別するアドレスがありません: Cの列挙値のメモリ位置

    コンパイル時定数はアドレスを必要としません。アドレスは変更できないものには役に立たないからです。

GCCは、enumメンバーの使用を、コンパイル時にAssemblyの即時値に単純に置き換えます。考慮してください:

_#include <stdio.h>

enum E {
    E0 = 0x1234,
    E1 = 0x1234
};
int i = 0x5678;

int main() {
    printf("%d\n", E0);
    printf("%d\n", E1);
    printf("%d\n", i);
    return 0;
}
_

GCC 4.8 x86_64を使用してコンパイルおよび逆コンパイルします。

_gcc -c -g -O0 -std=c89 main.c
objdump -Sr main.o
_

出力には以下が含まれます。

_    printf("%d\n", E0);
   4:       be 34 12 00 00          mov    $0x1234,%esi
  ...
    printf("%d\n", E1);
  18:       be 34 12 00 00          mov    $0x1234,%esi
  ...
    printf("%d\n", i);
  2c:       8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 32 <main+0x32>
                    2e: R_X86_64_PC32       i-0x4
  32:       89 c6                   mov    %eax,%esi
_

したがって、次のことがわかります。

  • enumメンバーはイミディエート_$0x1234_として使用されるため、どこから来たのかを知ることは不可能です
  • ただし、変数iはメモリ0x0(%rip)(再配置される)から取得されるため、アドレスによって2つの変数を区別できます。

他の答えを補足するために、特定のenumの異なる列挙に同じ値を使用することの実用的な例を示します。

enum slots_t {
    SLOT_FIRST = 0,
    SLOT_LEFTARM = SLOT_FIRST,
    SLOT_RIGHTARM = 1,
    SLOT_TORSO = 2,
    SLOT_LEFTLEG = 3,
    SLOT_RIGHTLEG = 4,
    SLOT_LAST = SLOT_RIGHTLEG
};

次に、あなたのコードで行うことができます:

for (int i = SLOT_FIRST; i <= SLOT_LAST; ++i) { }
10

それは哲学的(またはそうではない)

#define ZILCH 0
#define NADA  0

同じ名前の異なる名前を使用することが理にかなっている多くの用途があります。

8
Jens

列挙定数の名前は、実際の値自体ではなく、値の割り当てに使用されます。今日に値0を割り当てると、出力値は0になります。また、はい、MONとTUEの両方の値は0になり、残りの値にはWED = 1 THU = 2などの値が割り当てられます。

2
heretolearn