为什么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之后,也总是适合与x
和y
相同的类型。
因此,在第一种情况下, gcc
似乎可以断言操作永远不会导致值更改,但由于某种原因,第二种情况不能这样做。
作为旁注,clang并没有发出任何警告。
输入系统
-
在测试系统(x86-64)上,
char
类型已签名。 请注意,它仍然是与signed char
不同的类型。 -
x % y
由于整数提升规则,在这两种情况下,x
和y
都被提升为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); }
-
从
int
到char
,unsigned char
和signed 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个函数有一个从int
到char
/ unsigned char
的隐式转换,就像bar1
和bar2
。
价值分析
如果我们使用符号r = x % y
则r
与x
和|r| ∈ [0, |y|)
具有相同的符号 |r| ∈ [0, |y|)
。
-
如果
x
和y
的类型为unsigned char
则r ∈ [0, CHAR_MAX)
。r
适合unsigned char
。 所以不需要警告。 -
如果
x
和y
的类型为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喜欢在我见过的所有实现中签名。)
从int
到char
的隐式转换可能会导致char
溢出。
我们可以通过写return (char)(x % y);
来使编译器静音return (char)(x % y);
。 这只会隐藏潜在的错误。 您必须在代码中确保在添加此类显式转换之前永远不会发生溢出。