我试图针对左移负数的未定义行为生成警告。根据{{3}}的答案,C中负数的左移是不确定的。
E1 << E2的结果是E1左移E2位位置;空位用零填充。如果E1具有无符号类型,则结果的值为E1×2E2,与结果类型中可表示的最大值相比,模减少了1。如果E1具有带符号类型且非负值,并且E1×2E2在结果类型中可表示,则这是结果值;否则,行为是不确定的。
我试图理解为什么我没有收到以下代码的警告:
x << 3
gcc -Wall (版本9.1.0)
int main ()
{
int x= -4, y, z=5;
y = z << x;
y = x << 3;
return y;
}
另外,我也没有警告左移负数
z << x
答案 0 :(得分:1)
在5 << -4
中,针对x86-64的GCC 9.1.0和带有lang-1001.0.46.4的Apple LLVM 10.0.1均发出警告消息(GCC的“左移计数为负”,而“ shift”为计数为负”(对于LLVM-clang)。在-4 << 3
中,GCC不会发出警告,但是LLVM-clang会发出警告(“未定义移负值”)。
在这两种情况下,C标准都不需要诊断,因此编译器是否需要这样做是实现质量的问题(或者可能是编译器通过定义负值的左移来扩展C,因此需要这样做)。不认为这是一个错误)。
当操作数不是常量时,例如z << x
和x << 3
,我们可能会猜测编译器无法看到操作数为负,因此它不会发出警告。通常,这些表达式可能具有以下定义的行为:如果x
不为负且在合适的范围内(在前一种情况下,其宽度不大于z
的宽度,并且不大到{{1 }}在后一种情况下溢出),行为已定义(x << 3
可能溢出的除外)。 C标准并未说未定义可能具有负值的类型的左移,即即带符号的类型是未定义的,只是未定义具有负值的左移。因此,只要z << x
是表达式中的签名类型,例如x
或z << x
,编译器发出警告消息都是错误的。
当然,在给定x << 3
且初始化与表达式int x = -4
和x
之间z << x
没有任何变化的情况下,编译器可以推断出这些偏移是在此特定情况下未定义,并发出警告。该标准并不需要这样做,而编译器是否这样做完全取决于编译器实现的质量。
答案 1 :(得分:0)
在C89中,为左操作数的所有整数指定了将任何值左移小于字长的量的行为,即使对于负数左操作数的指定行为通常在非条件下通常不理想。 -two's-complement补充平台,或在错误乘以平台上(如果迭代乘以2会溢出)的情况。
为避免要求实现以不理想的方式运行,C99的作者决定允许实现在他们认为合适的情况下处理此类情况。 C99标准的作者不是希望在所有可能有充分理由偏离C89行为的地方进行猜测,反而会希望实现会遵循C89行为,而没有充分的理由 em>,无论标准是否强迫他们这样做。
如果标准作者打算在C89行为合理的情况下对左移运算的处理进行任何更改,则应在已发布的Rationale文档中提及更改。没有任何东西。从这种遗漏中,我唯一的推断是,从“以一种几乎总是有意义的特殊方式进行行为”变为“如果实现者判断它是有道理的,则以这种方式进行行为,否则将以其他方式处理代码实施者认为合适的方式”被认为不足以证明评论的合理性。标准的作者没有以任何方式强制要求行为的事实,这意味着他们不希望通用的二进制补码实现不会像以往那样继续处理这种变化,也不希望程序员不应这样做。有权获得类似的期望。
答案 2 :(得分:0)
在这种情况下,编译器不发出警告的简短解释是因为它不是必需的。
标准中“未定义”的定义也明确指出“不需要诊断”。这意味着在这种情况下,该标准不需要实施即可发出诊断。原因是,从技术上讲,编译器可能无法在合理的时间内或(在某些情况下)根本无法检测到所有未定义行为的实例。有些情况只能在运行时检测到。编译器是复杂的代码段,因此,即使人类可以轻松识别问题,编译器也可能不会(另一方面,编译器也可以检测出人类无法轻易发现的问题)。
当不需要诊断程序时,在编译器发出警告之前,必须完成几件事-所有酌情决定的事情。
首先发生的事情就是“实施质量”问题-供应商或编译器开发人员选择编写检测特定情况的代码以及发出警告的其他代码
这两个步骤(检测案例和对其进行警告)是分开的,并且是完全酌情决定的-该标准不需要诊断,因此,即使编写了检测特定案例的代码,仍然不需要编译器发布关于它的警告。实际上,即使检测到问题,编译器开发人员也可能出于各种原因选择不发出警告。一些警告是关于极少数情况的,因此发布这些警告是不值得的。某些警告具有固有的“误报”率,这往往会导致开发人员向编译器供应商发送有关不必要警告的错误报告-因此默认情况下将编译器配置为不发出警告。
发出警告的下一个要求是,编译器的用户必须选择显示警告。由于开发人员对厂商的强烈游说,大多数现代编译器都是按DEFAULT配置的,因此它们仅发出相对较少的警告。因此,希望从编译器获得尽可能多帮助的开发人员需要显式启用它。例如,通过在gcc和clang中使用-Wall
选项。