C11标准ISO / IEC 9899:2011(E)规定了§6.5.16.1/ 1中简单指配的以下约束:
以下其中一项应成立:
- 左操作数具有原子,限定或非限定算术类型,右边有 算术类型;
- 左操作数具有原子,限定或非限定版本的结构或联合 类型与右侧类型兼容;
- 左操作数具有原子,限定或非限定指针类型,并且(考虑到 两个操作数都是左值操作数在左值转换后的类型 指向兼容类型的限定或非限定版本的指针,以及指向的类型 左边的所有限定符都是右边所指的类型;
- 左操作数具有原子,限定或非限定指针类型,并且(考虑到 左值操作数在左值转换后将具有的类型)一个操作数是一个指针 到对象类型,另一个是指向合格或非限定版本的指针 void,左边指向的类型具有指向的类型的所有限定符 在右边;
- 左操作数是原子,限定或非限定指针,右边是null 指针常数;或
- 左操作数的类型为atomic,qualified或nonqualified
_Bool
,右边是指针。
我感兴趣的是双方都指向与void
不同的不兼容类型的情况。如果我理解正确,这应该至少调用UB,因为它违反了这个约束。不兼容类型的一个示例应该是(根据§6.2.7和§6.7.2)int
和double
。
因此,以下程序应违反:
int main(void) {
int a = 17;
double* p;
p = &a;
(void)p;
}
gcc和clang都警告“-Wincompatible-pointer-types”,但不要中止编译(使用-std=c11 -Wall -Wextra -pedantic
进行编译)。
同样,以下程序只会导致“-Wint-conversion”警告,同时编译得很好。
int main(void) {
int a;
double* p;
p = a;
(void)p;
}
来自C ++,我预计这些测试用例中的任何一个都需要使用强制转换来编译。是否有任何理由为什么这两个计划都是标准合法的?或者,即使通过明确使用-std=c11
代替-std=gnu11
来禁用有趣的GNU C扩展,支持此代码样式是否至少有重要的历史原因?
答案 0 :(得分:4)
您的代码示例和您对标准的引用不符。这个例子是初始化和6.5.16关于赋值的讨论。
令人困惑的是匹配类型要求在约束部分6.5.16中用于赋值,但在语义部分(6.7.9)中仅用于初始化。因此编译器有“权利”不发布初始化诊断。
在C中,约束违规只需要“诊断”,编译器可能会继续编译,但不能保证生成的可执行文件是有效的。
在我的平台上进行Debian测试,两个编译器都给了我一个没有任何选项的诊断,所以我猜你的安装必须很旧并且已经过时了。
答案 1 :(得分:4)
为什么这两个计划都是合法的标准?
这些计划并非“标准合法”。它们包含约束违规,您已经从标准中引用了正确的文本。
编译器通过生成约束违规诊断来符合标准。在违反约束或其他错误程序的情况下,标准不要求编译中止。
它没有用尽可能多的单词说,但唯一合理的结论是,由于包含约束违规的程序而生成的任何可执行文件都具有完全未定义的行为。 (虽然我看到有人试图争辩)。
推测如下:C(和C ++)用于多种用途;有时人们希望他们的机器使用“高级装配工”,而不关心便携性或标准。据推测,编译器供应商将默认值设置为他们认为目标受众更喜欢的内容。
答案 2 :(得分:3)
请求检查严格标准一致性并拒绝编译不一致代码的编译器标志(gcc和clang)是-pedantic-errors
:
$ gcc -std=c11 -pedantic-errors x.c
x.c: In function ‘main’:
x.c:3:15: error: initialization from incompatible pointer type [-Wincompatible-pointer-types]
double* p = &a;
^
锵:
$ clang -std=c11 -pedantic-errors x.c
x.c:3:11: error: incompatible pointer types initializing 'double *' with an
expression of type 'int *' [-Werror,-Wincompatible-pointer-types]
double* p = &a;
^ ~~
1 error generated.
野外典型C代码中很大一部分(至少可以说是)是不一致的,因此-pedantic-errors
会导致大多数C程序和库无法编译。
答案 3 :(得分:2)
不,是
<小时/> 这真的很简单。
不,标准不合法。是的,重要的历史原因。
C最初甚至没有演员阵容。作为一种系统编程语言,使用它作为一个超级强大的美化汇编程序不仅合理,而且是当天最佳实践和真正“唯一实践”。关键信息应该能够揭示事物:实施或执行规范确实不是编译器的工作。实际上,规范实际上只是一个建议。编译器的实际工作是编译所有已写入的C代码,包括C11之前,C99之前,C89之前,甚至K-amp之前的R。这就是有这么多可选限制的原因。具有现代代码风格的项目开启严格符合模式。
在标准中定义了C的方式,并且在实践中使用了C的方式。所以你可以看到,编译器根本无法拒绝构建代码。
几十年来,开发人员一直在转向可移植的,严格符合规范的代码,但是当C首次出现时,它的使用有点像一个非常强大的强大汇编程序,它在地址算术和类型惩罚方面是一个开放的季节。当时的程序大多是一次为一个架构编写的。