比较无符号和有符号类型不会产生警告(使用const)

6
简单来说,如果我在使用GCC编译时测试签名变量和无符号变量,当使用-Wall选项时,会得到一个警告。
使用以下代码:
#include <stdio.h>

int main(int argc, char* argv[])
{
    /* const */ unsigned int i = 0;
    if (i != argc)
        return 1;
    return 0;
}


我收到了这个警告:
<source>: In function 'int main(int, char**)':
<source>:6:8: warning: comparison of integer expressions of different signedness: 'unsigned int' and 'int' [-Wsign-compare]
    6 |  if (i != argc)
      |      ~~^~~~~~~
Compiler returned: 0

然而,如果我取消注释这个const,编译器就会满足。我可以在几乎每个GCC版本上重现这个问题(请参见https://godbolt.org/z/b6eoc1)。这是GCC的一个bug吗?


1
为什么会是一个 bug 呢?编译器只需要诊断很少的情况,而你的情况不是其中之一。另一方面,编译器可以自由地发出任何额外的诊断。即使是没有帮助的。默认情况下,编译器警告与 const 更准确是一种特性,而不是 bug。 - John Bollinger
gcc 在同一表达式中混合使用有符号和无符号类型时警告效果不佳。您可以使用“-Wconversion”选项获取更多此类警告,但该选项会产生大量误报,因此这是令人讨厌的原因之一。 - Lundin
@JohnBollinger:关于“为什么这会是一个错误?编译器只需要满足很少的情况就可以进行诊断...”:因为bug的定义是不符合规范,而C标准并不是GCC的规范。它是规范的一部分,通过引用进行了合并,但GCC有自己的文档说明它应该做什么和不应该做什么,包括额外的警告。如果未能发出其文档中规定会发出的警告,则会出现错误。 - Eric Postpischil
你的Godbolt链接是针对GCC的。在GCC的C和C++模式下,行为是不同的。 - Eric Postpischil
@EricPostpischil,我认可您对“bug”的定义是合理的,但并不是唯一可行的,也不是问题所暗示的。如果这确实是OP在提问时所考虑的,那么您给出的回应以及对问题的澄清更新将是他们做出合理回应的方式。 - John Bollinger
4个回答

5

我认为你缺失的是编译器优化。如果没有加上const,该变量就是一个变量,这意味着它可以被改变。由于这是一个无符号整数,这意味着它确实可以比整数更大。

const unsigned int i = 2147483648;

如果您将大于int最大值的值赋给无符号整型,将会收到错误的返回。

但是如果它是const,编译器知道它的值,知道它不会改变,因此比较就不会有问题。

如果您查看汇编代码,您将看到没有const时它实际上会取变量的值进行比较:

movl    $0, -4(%rbp)
movl    -20(%rbp), %eax
cmpl    %eax, -4(%rbp)

现在,如果变量被声明为const,它将完全不会受到影响,只取值:

movl    $0, -4(%rbp)
cmpl    $0, -20(%rbp)

这与问题无关。在两种情况下都没有工作 https://godbolt.org/z/nTdhnY - 0___________
1
如果它不是const,编译器会警告你该比较可能会导致不良后果,如果它是const,并且值的范围是fine的,则不存在该错误的可能性,因此也没有警告。 - Marcell Juhász
gcc和Ubuntu版本:gcc(Ubuntu 9.3.0-17ubuntu1〜20.04)9.3.0,我可以在此处复制相同的警告,但那没有多大意义。试试看吧。 - Marcell Juhász
哦,是的,我正在使用我的笔记本电脑,而不是在线工具。 - Marcell Juhász
你可能是对的,但如果它与const无关,则这是一种巧合,但如果它是一个const,则警告会消失。有点奇怪。 - Marcell Juhász
显示剩余4条评论

2
我认为这是-Wsign-compare选项中编译器的一个bug。
通过使用-Wall -Wextra -O3编译您的示例进行测试。添加-O3后,警告在const情况下突然消失。即使生成的机器代码与或不带const相同。这没有任何意义。
显然,const和生成的机器代码都不会影响C操作数的有符号性,因此警告不应该根据类型限定符或优化器设置而不一致地出现。

我倾向于同意。Clang没有这个“bug”。对我来说,这似乎是一个(非常小的)编译器错误。 - elcuco

0

只需简单地使用-Wall -Wextra,您就可以获得警告信息。

我建议使用-Wall -Wextra -pedantic编译器选项。

https://godbolt.org/z/TvqeKn

编辑

作为对非常不友好和不友善的OP评论的澄清,-Wextra启用以下警告,包括OP想要的那个。

enter image description here

warning: comparison of integer expressions of different signedness:
 'unsigned int' and 'int' [-Wsign-compare]
    9 |     if (i != argc)

1
这并没有真正回答问题。即使在-Wall下,这也应该被捕获。我错过了什么? - elcuco
2
@elcuco,你为什么认为应该加上-Wall呢?你写了启用命令行选项集合警告的gcc部分吗?这个警告是通过-Wextra启用的,它回答了这个问题。 - 0___________
在生产构建中是这样,但在开发过程中,我认为它走得太远了。 - 0___________
@Lundin 我的意思是:编写代码时,我更喜欢即使有一些不相关的警告,也要进行编译,稍后再进行排序。当我拥有产品版本时,所有警告都被视为错误。 - 0___________
@Lundin 是的,我认为那部分是显而易见的,因为它行不通。 :-) 话虽如此,除非必须,否则强烈建议不要使用扩展,因为这会使代码不可移植,通常没有什么好处。 - Konrad Rudolph
显示剩余6条评论

0

这个问题被标记为C,但链接到了一个C++ Godbolt示例。下面是一个表格,显示了何时发出警告:1

C非常量 C常量 C++非常量 C++常量
默认警告
-Wall
-Wall -Wextra

因此,在C中,无论是否有const限定符,GCC都会在-Wextra中提供警告。

在C++中,GCC在-Wall中提供警告,但将带有const限定符的对象视为已知值,可以抑制警告。

GCC文档中提到了-Wsign-compare

警告当有符号值和无符号值之间的比较在将有符号值转换为无符号值时可能会产生不正确的结果。在C++中,此警告也由-Wall启用。在C中,这也由-Wextra启用。

注意,它并没有说当有符号值和无符号值之间进行比较时就会发出警告,而是在这样的比较可能会产生不正确结果时才会发出警告。因此,如果对象的定义使得比较不可能产生不正确结果,则不提供警告并不是一个 bug。

单词“could”留下了编译器对对象的“了解”的余地。不能确定 C const 案例不能产生不正确的结果可能被描述为 bug,尽管更好的描述可能是缺陷。

脚注

1表格中的“Const”特指一个被const修饰且其值可以通过可见定义立即提供给编译器的对象。我没有测试过这样的情况,例如,一个标识符被声明为一个const限定的对象,但其定义在另一个翻译单元中。


尽管具有说明性,GCC在各种情况下的行为表本身并不能回答问题(“这是GCC中的一个错误吗?”)。鉴于您已经断言术语“错误”应被解释为与规范的偏差,我想您可能忘记将该表与相关规范配对。 - John Bollinger
具有const限定符的对象并不总是具有已知值。(需要在作用域中有该对象的定义才能知道其值。)如果值未知,则警告不会被抑制,这是合理的。 - Ian Abbott
我刚刚为了这个评论使用了OP的godbolt,现在我意识到它是使用C++编写的。但是昨天我玩的时候我使用的是C的gcc。无论如何,编译器不能假设关于argc的任何事情,即使C有一个规则专门说明它应该是非负的。将其替换为extern int a; .. if(i != a)并没有改善情况。https://godbolt.org/z/zbefsj。尽管编译器显然无法对外部变量进行任何操作,但使用-O3警告消失了,即使从RAM中明确加载了未知值的`a` mov edx,DWORD PTR a [rip] - Lundin
@Lundin:关于“……编译器不能假设argc的任何事情,即使C有一个规则”的问题:相反,编译器可以将C标准的语句视为公理。然而,这是无关紧要的,如下所述。关于“extern int a; .. if(i != a)”:这不是一个错误,因为任何aint值都会产生期望的结果:即使在转换为unsigned后,只有当a不为零时,i != a才为真。如果且仅当a为零时,将a转换为unsigned会产生零,因此比较永远不会提供与数学ia不同的结果。 - Eric Postpischil
@EricPostpischil 我正在使用纯C代码(基于gcc8.2的esp32编译器)复现这个问题。Windows和Linux本地交叉编译器的行为不同。对我来说,这似乎是一个端口/编译器错误。不过还是感谢您提供的详细信息。 - elcuco
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接