为什么GCC的-Wconversion对char和unsigned char的行为不同?

考虑

U8 foo(U8 x, U8 y) { return x % y; } 

如果U8(x和y的类型是char或unsigned char),GCC的-Wconversion表现不同:

gcc -Wconversion -c test.c -DU8='unsigned char'

(没有警告)

 gcc -Wconversion -c test.c -DU8=char test.c: In function 'foo': test.c:2:14: warning: conversion to 'char' from 'int' may alter its value [-Wconversion] return x % y; ~~^~~ 

但是根据我的理解,在两种情况下,x经过整数提升(到int或unsigned int),因此在两种情况下它都将从int转换为返回类型(char或unsigned char)。

为什么会有区别?

额外问题:如果你启用了ubsan(-fsanitize = undefined),那么GCC会在两种情况下都发出-Wconversion。

编辑

没有论证x,y经历整数提升然后需要转换为结果类型,因此无需解释。

这里唯一的问题是为什么GCC对不同类型的表现不同 。 答案将涉及对GCC内部的一些见解。

TLDR

仅使用有关所涉及类型的信息, gcc应警告两种情况,因为从int (较大类型)转换为char / unsigned char (较小类型)

使用有关可能值的信息(范围分析) gcc应警告无,因为x % y的结果,即使在促销到int之后,也总是适合与xy相同的类型。

因此,在第一种情况下, gcc似乎可以断言操作永远不会导致值更改,但由于某种原因,第二种情况不能这样做。

作为旁注,clang并没有发出任何警告。


输入系统

  • 在测试系统(x86-64)上, char类型已签名。 请注意,它仍然是与signed char不同的类型。

  • x % y由于整数提升规则,在这两种情况下, xy都被提升为int 。 结果x % y的类型为int

  • 如果我们将所有隐式转换显式化,那么我们得到:

     unsigned char foo1(unsigned char x, unsigned char y) { return (unsigned char)((int) x % (int) y); } char foo2(char x, char y) { return (char)((int) x % (int) y); } 
  • intcharunsigned charsigned char隐式转换使用-Wconversion触发警告:

    -Wconversion

    警告可能会改变值的隐式转换。 这包括[..]和转换为较小的类型

    实际上,这两个函数都会导致生成警告:

     char bar1(int a) { return a; // warning: conversion from 'int' to 'char' may change value [-Wconversion] } unsigned char bar2(int a) { return a; // warning: conversion from 'int' to 'unsigned char' may change value [-Wconversion] } 

所以只使用类型信息我们应该得到两个警告,因为我们的2个函数有一个从intchar / unsigned char的隐式转换,就像bar1bar2

价值分析

如果我们使用符号r = x % yrx|r| ∈ [0, |y|)具有相同的符号 |r| ∈ [0, |y|)

  • 如果xy的类型为unsigned charr ∈ [0, CHAR_MAX)

    r适合unsigned char 。 所以不需要警告。

  • 如果xy的类型为char

    • CHAR_MIN = -CHAR_MAX - 1
    • max(|y|) = CHAR_MAX + 1
    • |r| ∈ [0, max(|y|))
    • |r| ∈ [0, CHAR_MAX + 1)
    • r ∈ (-CHAR_MAX - 1, CHAR_MAX + 1)

    r适合char所以不需要警告。

所以我在争论的是,即使在所有整数提升和隐式转换之后x % y的结果总是适合U8


你可以看看这个神杖

如你所说, x % y涉及两个操作数到int隐式类型转换(整数提升规则/通常的算术转换)。 操作的结果是int类型。

-Wconversion关注表达式中签名的隐式更改,因为这些可能是无意的。 当您在有符号和无符号类型之间进行转换而没有显式强制转换时,它会发出警告。 当从较大类型(有符号或无符号)隐式转换为较小类型时,它也显然会警告溢出的潜在问题。

char类型具有实现定义的签名,它可以是未签名的或签名的.GCC喜欢在我见过的所有实现中签名。)

intchar的隐式转换可能会导致char溢出。

我们可以通过写return (char)(x % y);来使编译器静音return (char)(x % y); 。 这只会隐藏潜在的错误。 您必须在代码中确保在添加此类显式转换之前永远不会发生溢出。