const char *配列初期化コンマがない場合にコンパイラ警告を生成します


53

Cコードで文字列リテラルテーブルをよく使用しています。これらのテーブルはすべて次のようになります。

static const char* const stateNames[STATE_AMOUNT] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};

上記のコードの問題は、テーブルが長くなり、開発中に変更された場合、時々カンマを忘れることです。コードはコンマがないため問題なくコンパイルされますが、最後の文字列がに設定されているため、プログラムがクラッシュしNULLます。検証にはMinGWおよびKeilコンパイラを使用しました。

コンマが欠落している場合、私の初期化に対してコンパイラ警告を生成する方法はありますか?


1
このテーブルに状態を追加するのを忘れた場合はどうなりますか?
Jeroen3

1
@ Jeroen3 trueこれは同じエラーを引き起こします。静的アサートを使用してSTATE_AMOUNTに対してリストの長さをテストすると、この問題も解決されます。
Jonny Schubert

回答:


62

すべてを包む const char*次のスニペットに示すように、を括弧で囲むと問題が解決します。

static const char* const stateNames[5] =
{
    ("Init state"),
    ("Run state"),
    ("Pause state")     //comma missing
    ("Pause state3"),
    ("Error state")
};

コンマを忘れると、次のようなコンパイルエラーが発生します。 error: called object is not a function or function pointer

ライブデモ


コンマを忘れた場合、実際に起こることは、Cが次のコンマまたは配列の終わりまで実際に2つ(またはそれ以上)の文字列を連結するということです。たとえば、次のようにコンマを忘れたとします。

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state" //comma missing
    "Pause state3" //comma missing
    "Error state"
};

int main(void)
{  
    printf("%s\n", stateNames[0]);
    return 0;    
}

これがgcc-9.2生成するものです(他のコンパイラは同様のコードを生成します):

.LC0:
        .string "Init state"
        .string "Run state"
        .string "Pause statePause state3Error state" ; oooops look what happened
        .quad   .LC0
        .quad   .LC1
        .quad   .LC2
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    puts
        mov     eax, 0
        pop     rbp
        ret

最後の3つの文字列が連結され、配列が期待した長さではないことは明らかです。


33

予期しない結果が発生した場合は、コンパイラに配列をカウントさせてエラーメッセージを生成させることができます。

enum { STATE_AMOUNT = 4 };

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state"    // <--- missing comma
    "Error state",
};

_Static_assert( sizeof stateNames / sizeof *stateNames == STATE_AMOUNT,
        "oops, missed a comma" );

実装するアイデアについては、このスレッド参照してください_Static_assertコンパイラが非常に古く、サポートしていない場合する。

おまけとして、これは新しい状態を追加したときに文字列テーブルの更新を忘れた場合にも役立ちます。ただし、Xマクロも調べてみてください。


くそー...これは私がタイプしようとしていた正確な答えでした!
溶接機

11

これを解決するために、明示的なサイズの配列への参照を常に使用しました。

// no explicit size here
static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;

http://coliru.stacked-crooked.com/a/593fc2eac80782a6

main.cpp:10:32: error: reference to type 'const char *const [5]' could not bind to an lvalue of type 'const char *const [4]'
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;

4
静的アサーションは、はるかに洗練されたソリューションのようです。静的アサーションが言語の一部として実装される前に、これを行う癖があったと思いますか?配列の予想サイズを検証する静的アサーションに比べて、これの利点はまだありますか?
コーディグレイ

2
@CodyGray:ええ、これはあなたが言及したように、今は静的なアサート
でし

9

これはコンパイラーを助けにはしませんが、以下のように書くと、人間がコンマを落とさないように簡単になります。

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};

3
最後に何かを追加するのも簡単です。コンマを追加するために前の行を編集する必要はありません。(カンマが欠落している主な理由。)
datafiddler

@datafiddler:そうですね。また、SQL SELECTコマンドで列のリストを微調整したり、コメントを付けたり、外したりするときにも役立ちます。あなたはしばしば最後のものを変更したいです。最初のものを変更することはめったにありません。これにより、1つの項目をコメント化するために複数の行を変更する必要がなくなります。
JonathanZは
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.