CとC ++で異なる動作をする列挙型定数


81

なぜこれを行うのですか?

#include <stdio.h>
#include <limits.h>
#include <inttypes.h>

int main() {
    enum en_e {
        en_e_foo,
        en_e_bar = UINT64_MAX,
    };
    enum en_e e = en_e_foo;
    printf("%zu\n", sizeof en_e_foo);
    printf("%zu\n", sizeof en_e_bar);
    printf("%zu\n", sizeof e);
}

4 8 8Cおよび8 8 8C ++(4バイトintのプラットフォーム上)で印刷しますか?

このUINT64_MAX割り当てにより、すべての列挙定数が少なくとも64ビットに強制されますがen_e_foo、プレーンCでは32のままであるという印象を受けました。

不一致の理由は何ですか?


1
どのコンパイラ?それが違いを生むかどうかはわかりませんが、違いはあるかもしれません。
マークランサム2017年

@MarkRansom gccが思いついたが、clangは同じように動作する。
PSkocik 2017年


3
「4バイトintのプラットフォーム上」プラットフォームだけでなく、型幅を決定するコンパイラーです。これですべてかもしれません。(キースの答えによると、実際にはそうではありませんが、一般的にそのような可能性に注意してください)
軌道上のライトネスレース2017年

1
@PSkocik:実際には変更ではありません。この質問で、cc ++の両方の有効な使用法が見つかっただけです(特定のコードが2つの間で異なる動作を引き起こす理由を尋ねます)。また、OK:C ++からCライブラリを呼び出す方法、およびCから呼び出すことができるC ++を作成する方法を尋ねます。非常にOKではありません:Cの質問をし、「より多くの目玉を取得する」にC ++タグをスローします。また、OKではありません。C++の質問をし、後から「Cについても回答するようにしてください」と質問します。(そして通常の不平を言う人にとっては-非常に大丈夫ではありません:コードは両方の標準に存在する関数を使用しているため、C ++タグをCタグに変更します)
BenVoigt19年

回答:


80

Cでは、enum定数はタイプintです。C ++では、列挙型です。

enum en_e{
    en_e_foo,
    en_e_bar=UINT64_MAX,
};

Cでは、これは制約違反であり、診断が必要です(を超える場合 UINT64_MAXINT_MAX、おそらくそうなります)。ACコンパイラは、プログラムを完全に拒否するか、警告を出力してから、動作が定義されていない実行可能ファイルを生成する場合があります。(制約に違反するプログラムが必ずしも未定義の動作をすることは100%明確ではありませんが、この場合、標準は動作が何であるかを述べていないので、それはまだ未定義の動作です。)

gcc6.2はこれについて警告しません。clangはそうします。これはgccのバグです。標準ヘッダーのマクロが使用されている場合、一部の診断メッセージが誤って禁止されます。バグレポートを見つけてくれたGrzegorzSzpetkowskiに感謝しますhttps://gcc.gnu.org/bugzilla/show_bug.cgi?id = 71613

C ++では、各列挙型には基になる型がありintます。これは整数型です(必ずしも)。この基になる型は、すべての定数値を表すことができなければなりません。したがって、この場合、en_e_fooen_e_barは両方ともタイプen_eであり、int幅が狭くても少なくとも64ビット幅である必要があります。


10
クイックノート:UINT64_MAX超えないINT_MAXためにintは、少なくとも65ビットが必要です。
Ben Voigt 2017年

10
本当に奇妙なことは、GCC(5.3.1)との警告を発していることである-Wpedantic18446744073709551615ULLはありませんでUINT64_MAX
nwellnhof 2017年

4
@dascandy:いいえ、int符号付きタイプである必要があるため、表現できるようにするには少なくとも65ビットである必要がありますUINT64_MAX(2 ** 64-1)。
キーストンプソン

1
@ KeithThompson、6.7.2.2は、「列挙子リストの識別子は、int型の定数として宣言されており、許可されている場合はどこにでも表示される可能性がある」と述べています。私の理解では、単一のC列挙型が宣言する定数は列挙型の型を使用しないため、そこからそれらを異なる型にすることはそれほど大きくありません(特に標準の拡張として実装されている場合)。
zneak 2017年

2
@AndrewHenle:en_e_bar列挙型より大きくはなく、en_e_foo小さくなっています。列挙型変数は最大定数と同じくらい大きかった。
Ben Voigt 2017年

25

そのコードは、そもそも有効なCではありません。

C99とC11の両方のセクション6.7.2.2は、次のように述べています。

制約:

列挙定数の値を定義する式は、として表現可能な値を持つ整数定数式でなければなりませんint

コンパイラ診断は制約違反であるため必須です。5.1.1.3を参照してください。

動作が未定義または実装として明示的に指定されている場合でも、前処理の変換ユニットまたは変換ユニットに構文規則または制約の違反が含まれている場合、準拠する実装は少なくとも1つの診断メッセージ(実装定義の方法で識別)を生成する必要があります-定義されています。


23

Cが、enum分離型であると考えられ、列挙子自体は常にタイプを持っていますint

C11-6.7.2.2列挙指定子

3列挙子リストの識別子は、int ..型の定数として宣言されています。

したがって、表示される動作はコンパイラ拡張です。

列挙子の値が大きすぎる場合にのみ、列挙子の1つのサイズを拡張することは理にかなっていると思います。


一方、C ++では、すべての列挙子enumはで宣言されているタイプを持っています。

そのため、すべての列挙子のサイズは同じである必要があります。そのため、全体のサイズenumが拡張され、最大の列挙子が格納されます。


11
これはコンパイラ拡張ですが、診断の生成の失敗は不適合です。
Ben Voigt 2017年

16

他の人が指摘したように、制約違反のため、コードの形式が正しくありません(C)。

GCCバグ#71613(2016年6月に報告)があり、いくつかの有用な警告がマクロで沈黙していると述べています。

システムヘッダーのマクロが使用されている場合、有用な警告は無音になっているようです。たとえば、以下の例では、警告は両方の列挙型に役立ちますが、1つの警告のみが表示されます。他の警告についても同じことが起こる可能性があります。

現在の回避策は、マクロの前に単項演算+子を付けることです。

enum en_e {
   en_e_foo,
   en_e_bar = +UINT64_MAX,
};

これにより、GCC4.9.2を使用するマシンでコンパイルエラーが発生します。

$ gcc -std=c11 -pedantic-errors -Wall main.c 
main.c: In function ‘main’:
main.c:9:20: error: ISO C restricts enumerator values to range ofint’ [-Wpedantic]
         en_e_bar = +UINT64_MAX

12

C11-6.7.2.2 / 2

列挙定数の値を定義する式は、として表現可能な値を持つ整数定数式でなければなりませんint

en_e_bar=UINT64_MAXは制約違反であり、これにより上記のコードは無効になります。診断メッセージは、C11ドラフトに記載されている実装を確認することによって生成する必要があります。

適合実装は、前処理翻訳ユニットまたは翻訳ユニットに構文規則または制約の違反が含まれている場合、少なくとも1つの診断メッセージ(実装定義の方法で識別される)を生成するものとします。[...]

GCCにバグがあり、診断メッセージを生成できなかったようです。(バグはGrzegorzSzpetkowskiによる回答で指摘されています


8
「未定義の振る舞い」は実行時の影響です。 sizeofコンパイル時の演算子です。ここにはUBはなく、たとえあったとしても、影響はありませんsizeof
Ben Voigt

2
intに収まらない列挙子はUBであるという標準引用符を見つける必要があります。私はその声明に非常に懐疑的であり、これが解決されるまで私の投票は堅実な-1のままです。
zneak 2017年

3
@Sergey:C標準は、実際には「列挙定数の値を定義する式は、intとして表現可能な値を持つ整数定数式でなければならない」と言っています。ただし、これに違反すると、UBではなく、制約違反になり、診断が必要になります。
Ben Voigt 2017年

3
@haccks:はい?これは制約違反であり、「前処理の翻訳ユニットまたは翻訳ユニットに構文ルールまたは制約の違反が含まれている場合、動作が未定義または実装定義として明示的に指定されています。」
Ben Voigt 2017年

2
オーバーフローと切り捨てには違いがあります。オーバーフローとは、期待される結果タイプに対して大きすぎる値を生成する算術演算があり、符号付きオーバーフローがUBである場合です。切り捨てとは、ターゲットタイプを開始するには大きすぎる値(などshort s = 0xdeadbeef)があり、動作が実装で定義されている場合です。
zneak 2017年

5

標準を調べたところ、6.7.2.2p2のため、プログラムはCの制約違反のようです

制約:列挙定数の値を定義する式は、intとして表現可能な値を持つ整数定数式でなければなりません。

7.2.5のため、C ++で定義されています。

基になる型が固定されていない場合、各列挙子の型はその初期化値の型になります。—列挙子に初期化子が指定されている場合、初期化値は式と同じ型であり、定数式は整数でなければなりません。定数式(5.19)。—最初の列挙子に初期化子が指定されていない場合、初期化値の整数型は指定されていません。—それ以外の場合、初期化値のタイプは、増分値がそのタイプで表現できない場合を除いて、前の列挙子の初期化値のタイプと同じです。その場合、タイプは、増分値を含むのに十分な未指定の整数型です。そのようなタイプが存在しない場合、プログラムは不正な形式です。


3
Cでは「未定義」ではなく、制約に違反しているため「不正な形式」です。コンパイラは、違反に関する診断を生成する必要があります。
Ben Voigt 2017年

@BenVoigt違いについて教えてくれてありがとう。回答で修正しました(他の回答でC ++標準からの引用を見逃したために作成しました)。
PSkocik 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.