【问题标题】:Signed/unsigned mismatch when comparing two unsigned values using a conditional operator使用条件运算符比较两个无符号值时有符号/无符号不匹配
【发布时间】:2012-10-02 00:05:26
【问题描述】:

我有以下 C 代码:

unsigned int a;
unsigned char b, c;
void test(void) {
    if (a < b)
        return;
    if (a < (b ? b : c))
        return;
}

当我编译它时(使用 Microsoft cl,来自 MS SDK 7,-W3 警告级别),第二个比较发出警告:C4018,有符号/无符号不匹配。第一次比较不会发出警告。

我检查了MS docs on the conditional operator,他们说如果两个操作数属于同一类型,则结果将属于同一类型,所以它应该作为第一个比较。我错过了什么吗?

UPD:gcc -Wall -Wextra -pedantic 测试,没有收到任何警告。

【问题讨论】:

  • 您的编译器选项中是否打开了所有编译器警告?
  • 是的,一个不方便的错误/功能。如果 VC 抱怨,我会仔细研究它并进行投射,并且不要再想它了。
  • @cowboydan 是的,-W3。如果我没有指定警告级别,则根本没有警告。
  • gcc 不会产生任何警告。

标签: c visual-c++ conditional-operator


【解决方案1】:

这可能是由于算术转换规则:首先,任何小于int 的整数类型转换等级(例如unsigned char)将提升为intunsigned int

结果是int 还是unsigned int 不(直接)取决于原始类型的签名,但它的范围:int 甚至用于无符号类型,只要所有值都可以表示,unsigned char 在主流架构上就是这种情况。

其次,由于两个操作数最终具有相同的转换等级,但一个是无符号的,另一个操作数也将转换为无符号类型。

从语义上看,你的表达方式

a < (unsigned int)(int)b

a < (unsigned int)(b ? (int)b : (int)c)

编译器显然足够聪明,可以注意到第一种情况不会导致问题,但第二种情况会失败。

Steve Jessop 的评论很好地解释了这是如何发生的:

我想在第一种情况下,编译器会认为,“我有一个比较运算符,其操作数类型是 unsigned intunsigned char。不需要警告,现在让我们应用提升,然后进行常规转换”。

在第二种情况下,它认为,“我有一个比较运算符,其操作数类型为 unsigned intint(我将其作为 RHS 上条件表达式的类型派生)。最好警告一下!”。

【讨论】:

  • 严格来说第二种情况等同于a &lt; (unsigned int)(int)(b ? (int)b : (int)c),因为条件运算符中有一个特殊的奇怪规则。 6.5.15/3 If both the second and third operands have arithmetic type, the result type that would be determined by the usual arithmetic conversions, were they applied to those two operands, is the type of the result. 当然,在这种特定情况下,结果并不重要。
  • @Lundin:我实际上考虑过包含该转换(以及推广第一个b),但出于教学原因决定不这样做;你的答案确实更完整,所以我+1
  • 我想在第一种情况下,编译器会认为,“我有一个比较操作数,其类型是 unsigned intunsigned char。不需要警告,现在让我们应用提升,然后是通常转换”。在第二种情况下,它认为,“我有一个比较操作数,其类型是 unsigned intint(我将其作为 RHS 条件表达式的类型派生出来)。最好警告一下!”。
  • @SteveJessop:希望你不介意我偷了你的好解释
  • @SteveJessop 实际上可能是这样,因为编译器不会强制执行所有隐式提升,如果它可以判断它们不会影响表达式的结果。假设它将代码直接转换为“将 a 和 b 显示为 32 位 CPU 寄存器,然后进行比较”。
【解决方案2】:

if (a &lt; b) 等于伪if (unsigned int &lt; unsigned char)

每当在表达式中使用 char 类型时,C 中的整数提升规则都会将其隐式转换为 int。完成后,你就有了

if (unsigned int &lt; int).

每当在一个表达式中使用两个相同等级但符号不同的整数时,有符号的整数会隐式转换为无符号的。这是由通常的算术转换决定的,也就是平衡

所以你的第一个表达式被转换为

if (unsigned int &lt; unsigned int)

在做任何事情之前。


在第二个表达式中,if (a &lt; (b ? b : c)) 等于伪

if (unsigned int &lt; (unsigned char ? unsigned char : unsigned char)).

对所有字符执行整数提升,因此它被隐式转换为

if (unsigned int &lt; (int ? int : int)).

然后,条件运算符的一个奇怪而晦涩的规则规定 ?: 运算符的第二个和第三个运算符必须与通常的算术转换保持平衡。在这种情况下,它们已经属于同一类型,所以什么也不会发生。我们最终得到了

if (unsigned int &lt; int)

再次发生平衡,结果将被评估为

if (unsigned int &lt; unsigned int)


当我用微软编译它时

当你用微软编译时,所有的赌注都没有了,他们的编译器在遵循标准方面很差。期待奇怪的、不合逻辑的警告。

【讨论】:

    【解决方案3】:

    C 和 C++ 之间的规则不同。使用 MSVC 编译 C 时可能很难判断合适的标准,但幸运的是在这种情况下 C89 和 C99 是相同的。

    在 C89 3.3.15 中:

    如果第二个和第三个操作数都有算术类型,通常 执行算术转换以使它们成为通用类型 结果就是那个类型

    在 C99 6.5.15/5 中:

    如果第二个和第三个操作数都具有算术类型,则结果 由通常的算术转换确定的类型, 它们是否应用于这两个操作数,是结果的类型。

    在 C++03 5.16/4 中:

    如果第二个和第三个操作数是左值并且具有相同的类型, 结果就是那个类型并且是一个左值

    因此,当您说“如果两个操作数属于同一类型,则结果将属于同一类型,因此它应该作为第一个比较”,这仅适用于 C++,不适用于 C。在 C 中该比较的 RHS 类型是 int。在 C++ 中,RHS 将是 unsigned char 类型的左值,如您所料。

    【讨论】:

    • 在左值到右值转换之后,即使在 C++ 中也不会应用整数提升吗?
    • @Cristoph:是的。在 C++ 中,&lt; 的操作数类型将是 unsigned intunsigned char然后 提升和转换适用于操作数。 C中&lt;的操作数类型为unsigned intint,然后对操作数进行提升和转换。
    • 我的“答案”实际上应该是一个评论。它说“如果两个操作数属于同一类型,则结果将属于同一类型……我错过了什么吗?” (取自 MS 的文档),而不是隐含的实际问题“为什么我会收到此警告?”。但是没有办法将这些标准引用放入评论中,因为你真正问的唯一问题是“我错过了什么吗?”那么你的问题的答案是“是”;-)
    • @SteveJessop 你说得对;如果我在 C++ 模式下编译它(使用-Tp),我在这里不会收到任何警告。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2011-07-21
    • 1970-01-01
    • 2012-01-23
    • 2016-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多