考虑
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;
~~^~~
但from what I understand在两种情况下x,y都经历整数提升(到int或unsigned int),因此在两种情况下它都将从int转换为返回类型(char或unsigned char)。< / p>
为什么会有区别?
额外问题:如果启用了ubsan(-fsanitize = undefined),那么GCC会在两种情况下都发出-Wconversion。
编辑:
没有论证x,y经历整数提升然后需要转换为结果类型,因此无需解释。
这里唯一的问题是为什么GCC对不同类型的行为方式不同。答案将涉及对GCC内部的一些见解。
答案 0 :(得分:4)
TLDR
仅使用有关所涉及类型的信息,gcc
应警告这两种情况,因为从int
(较大类型)转换为char
/ unsigned char
(较小类型)
使用有关可能值的信息(范围分析)gcc
应警告无,因为x % y
的结果,即使在提升为int之后,也总是适合与{{ 1}}和x
。
因此,似乎在第一种情况下y
可以声明操作永远不会导致值更改,但由于某种原因不能对第二种情况执行此操作。
作为附注,clang并没有警告任何人。
在测试系统(x86-64)上,gcc
类型已签名。请注意,它仍然与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
因此,仅使用类型信息我们应该收到两者的警告,因为我们的2个函数具有从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]
}
到int
/ char
的隐式转换,就像unsigned char
和{ {1}}。
如果我们使用符号bar1
,则bar2
与r = x % y
和r
具有相同的符号。
如果x
和|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
。
您可以查看此godbolt
答案 1 :(得分:1)
正如您所说,x % y
涉及将两个操作数隐式类型转换为int
(整数提升规则/通常的算术转换)。操作的结果是类型int
。
-Wconversion
关注表达式中签名的隐式更改,因为这些可能是无意的。当您在有符号和无符号类型之间进行转换而没有显式强制转换时,它会发出警告。当从较大类型(有符号或无符号)隐式转换为较小类型时,它也显然会警告溢出的潜在问题。
(char
类型具有实现定义的签名,可以是无符号签名或签名.GCC喜欢在我见过的所有实现中签名。)
从int
到char
的隐式转换可能会导致char
溢出。
我们可以通过编写return (char)(x % y);
来使编译器静音。这只会隐藏潜在的bug。您必须在代码中确保在添加此类显式转换之前永远不会发生溢出。