我有这样的代码:
$ 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!");
}
}
关于它的正确性有两种观点:
$ 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中:
可修改的左值是指没有数组类型、没有不完整类型、没有常量限定类型的左值,并且如果它是结构或并集,则没有任何具有常量限定类型的成员(递归地,包括所有包含的聚合或并集的任何成员或元素)。
所以海湾合作委员会的警告是正确的。
此外,条款6.5.16:2位于C99标准的"约束"部分,因此要求一致的编译器为违反该条款的程序发出诊断。它仍然是未定义的行为:在发出诊断后,编译器仍然可以执行它想要的操作。但必须有一个信息。因此,Clang在这里的行为不合规。
const
变量在初始化后不能修改,否则它是未定义的行为。
给变量x
一个具有定义行为的值的唯一方法是初始化它:
SX sx = { .x = 10 };
编辑:正如@Keith Thompson在下面评论的那样,在这种情况下,这不仅仅是未定义的行为:
C99§6.5.16分配运算符
限制
赋值运算符的左操作数应为可修改的左值。
这是一个限制,根据:
C99§5.1.1.3诊断
一致性实施应产生至少一条诊断信息(在实现定义的方式),如果预处理翻译单元或翻译单元包含违反任何语法规则或约束的行为,即使行为也被明确指定为未定义或实现定义。在其他情况下不需要生成诊断消息。
编译器必须为任何违反约束的程序发出诊断。
回到问题上来,gcc生成警告是正确的,而clang没有这样做。