哪个C99编译器(Clang vs. GCC)更接近const结构域的标准?

我有这样的代码:

$ cat test.c #include  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: 

哪个编译器是对的?

C99标准在6.5.16:2中说:

赋值运算符应具有可修改的左值作为其左操作数。

在6.3.2.1:1中:

可修改的左值是一个左值,它没有数组类型,没有不完整的类型,没有const限定类型, 如果是结构或联合,则没有任何成员(包括,递归地,任何成员)或具有const限定类型的所有包含的聚合或联合的元素

因此海湾合作委员会是正确的警告。

此外,条款6.5.16:2在C99标准的“约束”部分中,因此需要符合标准的编译器为违反该条款的程序发出诊断。 它仍然是未定义的行为:编译器仍然可以在发出诊断后执行它想要的操作。 但必须有一个信息。 因此,Clang在这里表现得不合规。

初始化后不能修改const变量,否则它是未定义的行为。

由于它是未定义的行为,我认为可以说gcc和clang都遵循标准。 (虽然gcc的选择似乎更好,但值得警告) (见下面的编辑

赋予变量x一个具有已定义行为的值的唯一方法是初始化它:

 SX sx = { .x = 10 }; 

编辑 :正如@Keith Thompson在下面评论的那样,在这种情况下,它不仅仅是未定义的行为:

C99§6.5.16赋值运算符

约束

赋值运算符应具有可修改的左值作为其左操作数。

这是一个约束,并根据:

C99§5.1.1.3诊断

如果预处理转换单元或转换单元包含违反任何语法规则或约束的情况,则符合的实现应生成至少一个诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实现 – 定义。 在其他情况下不需要产生诊断消息。

编译器必须为违反约束的任何程序发出诊断。

回到问题,gcc是正确的是生成警告,而clang没有这样做。