5

次のようなコードがあります。

$ cat test.c 
#include <stdio.h>
typedef struct
{
    const int x;
} SX;

static SX mksx(void)
{
    return (SX) { .x = 10 };
}

void fn(void)
{
    SX sx;
    while((sx = mksx()).x != 20)
    {
        printf("stupid code!");
    }
}

そして、その正しさについての2つの意見:

$ for i in gcc clang; do echo "$i SAYS:"; $i -c -std=c99 -pedantic -Werror test.c; done
gcc SAYS:
test.c: In function ‘fn’:
test.c:15:2: error: assignment of read-only variable ‘sx’
  while((sx = mksx()).x != 20)
  ^
clang SAYS:

どのコンパイラが正しいですか?

4

2 に答える 2

12

C99 標準は、6.5.16:2 で次のように述べています。

代入演算子は、左オペランドとして変更可能な左辺値を持つものとします。

および 6.3.2.1:1:

変更可能な左辺値は、配列型を持たず、不完全な型を持たず、const 修飾された型を持たず、構造体または共用体である場合、メンバー (再帰的にメンバーを含む) を持たない左辺値です。または、含まれているすべての集約または共用体の要素) を const 修飾された type で指定します。

したがって、GCC が警告するのは正しいことです。

さらに、節 6.5.16:2 は C99 標準の「制約」セクションに含まれているため、適合するコンパイラは、節に違反するプログラムの診断を発行する必要があります。これはまだ未定義の動作です。コンパイラは、診断が発行された後も必要なことを実行できます。しかし、メッセージがなければなりません。その結果、Clang はここで不適合な方法で動作しています。

于 2013-08-30T09:06:32.247 に答える
3

const変数は初期化後に変更できません。変更しないと、未定義の動作になります。

これは未定義の動作なので、gcc も clang も標準に準拠していると言えます。(gcc の選択の方が優れているように見えますが、注意が必要です)(以下の編集を参照)

x定義済みの動作で変数に値を与える唯一の方法は、変数を初期化することです。

SX sx = { .x = 10 };

EDIT:@Keith Thompsonが以下にコメントしているように、この場合の未定義の動作以上のものです:

C99 §6.5.16 代入演算子

制約

代入演算子は、左オペランドとして変更可能な左辺値を持つものとします。

これは制約であり、次のとおりです。

C99 §5.1.1.3 診断

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

コンパイラは、制約に違反するすべてのプログラムに対して診断を発行する必要があります。

質問に戻ると、gcc は正しいですが、clang は警告を生成しません。

于 2013-08-30T08:32:09.347 に答える