对我来说,这是一个真正的WTF,看起来像GCC中的一个错误,但我希望让社区看一看并为我找到解决方案。
这是我能想到的最简单的程序:
val resFirst = testFunc(Seq(1.0,2.0,3.0))
val resSecond = testFunc(List(1,2))
我正在尝试使用#include <stdio.h>
#include <stdint.h>
int main(void)
{
uint16_t i = 1;
uint16_t j = 2;
i += j;
return i;
}
标志在GCC上编译它,我正在使用我的大部分代码。
结果如下:
-Werror=conversion
此代码会发生同样的错误:
.code.tio.c: In function ‘main’:
.code.tio.c:9:7: error: conversion to ‘uint16_t {aka short unsigned int}’ from ‘int’ may alter its value [-Werror=conversion]
i += j;
错误是
uint16_t i = 1;
i += ((uint16_t)3);
为了清楚起见,这里的错误出现在.code.tio.c: In function ‘main’:
.code.tio.c:7:7: error: conversion to ‘uint16_t {aka short unsigned int}’ from ‘int’ may alter its value [-Werror=conversion]
i += ((uint16_t)3);
^
运算符上,而不是强制转换。
看起来+=
与+=
的运算符重载被搞砸了。或者我错过了一些微妙的东西?
供您使用:MCVE
编辑:更多相同的内容:
uint16_t
但.code.tio.c:8:6: error: conversion to ‘uint16_t {aka short unsigned int}’ from ‘int’ may alter its value [-Werror=conversion]
i = i + ((uint16_t)3);
至少有效......
答案 0 :(得分:32)
隐式转换的原因是+=
运算符与=
和+
的等效性。
来自C standard的第6.5.16.2节:
3 E1 op = E2形式的复合赋值等同于简单赋值表达式E1 = E1 op(E2),除了左值 E1仅评估一次,并且相对于 不确定顺序的函数调用,复合赋值的操作是单一的评估
所以这个:
i += ((uint16_t)3);
相当于:
i = i + ((uint16_t)3);
在此表达式中,+
运算符的操作数将提升为int
,并且int
被分配回uint16_t
。
第6.3.1.1节详细说明了原因:
2 以下内容可用于任何可能使用
int
或unsigned int
的表达式:
- 具有整数类型(
int
或unsigned int
除外)的对象或表达式,其整数转换等级小于或等于 等级int
和unsigned int
。- 类型为
_Bool
,int
,signed int
或unsigned int
的位字段。如果
int
可以表示原始类型的所有值(限制为 通过宽度,对于位字段),该值被转换为int
; 否则,它将转换为unsigned int
。这些被称为 整数促销。所有其他类型的整数不变 促销。
由于uint16_t
(a.k.a。和unsigned short int
)的排名低于int
,因此在用作+
的操作数时会提升这些值。
您可以通过分解+=
运算符并转换右侧来解决此问题。此外,由于促销,对值3的强制转换无效,因此可以删除:
i = (uint16_t)(i + 3);
但请注意,此操作会发生溢出,这是没有强制转换时发出警告的原因之一。例如,如果i
的值为65535,则i + 3
的类型为int
,值为65538.当结果转回uint16_t
时,将从此值中减去值65536值产生值2,然后将其分配回i
。
在这种情况下,此行为已得到很好的定义,因为目标类型是无符号的。如果目标类型已签名,则结果将是实现定义。
答案 1 :(得分:13)
任何算术运算符的参数都取决于N1570(最新的C11草案),§6.3.1.8中描述的通常的算术转换。与此问题相关的段落如下:
[关于浮点类型的一些规则]
否则,将对两个操作数执行整数提升。
因此,进一步查看如何定义整数促销,我们在§6.3.1.1p2中找到相关文本:
如果
int
可以表示原始类型的所有值(由宽度限制,对于a 位字段),该值转换为int
;否则,它将转换为unsigned int
。这些被称为整数促销。
所以,即使使用此代码:
i += ((uint16_t)3);
算术运算符的存在会导致操作数转换回int
。由于作业是操作的一部分,因此会将int
分配给i
。
这确实很有用,因为i + 3
实际上可能会溢出uint16_t
。
答案 2 :(得分:11)
i += ((uint16_t)3);
等于(1)
i = i + ((uint16_t)3);
最右边的操作数由强制转换从int
(整数常量3
的类型)显式转换为uint16_t
。之后,通常的算术转换(2)应用于+
的两个操作数,之后两个操作数被隐式转换为int
。 +
操作的结果是int
类型。
然后,您尝试在int
中存储uint16_t
,这正确导致来自-Wconversion
的警告。
如果您希望避免将int
分配给uint16_t
,可能的解决办法就是这样(MISRA-C兼容等):
i = (uint16_t)(i + 3u);
(1)这是所有复合赋值运算符的强制要求,C11 6.5.16.2:
E1 op = E2 形式的复合赋值等同于简单赋值表达式 E1 = E1 操作( E2 ),但左值 E1 仅评估一次,
(2)有关隐式类型促销的详情,请参阅Implicit type promotion rules。
答案 3 :(得分:3)
找到解释here:
joseph [at] codesourcery.com 2009-07-15 14:15:38 UTC
主题:Re:-Wconversion:不警告不大于目标类型的操作数2009年7月15日星期三,ian at airs dot com写道:
&GT;当然,它可以换行,但-Wconversion不是用于包装警告。
这是关于隐式转换更改值的警告;该 算术,更广泛的类型(故意或其他),不包装, 但是通过隐式转换返回char来改变值。如果 用户在算术中对int进行了显式强制转换,可能没有 怀疑警告是否合适。
发生警告是因为编译器让计算机使用比uint16_t
(int
更大的类型,通过整数提升)执行算术,并将值重新放回uint16_t
可以截断它。例如,
uint16_t i = 0xFFFF;
i += (uint16_t)3; /* Truncated as per the warning */
这同样适用于单独的赋值和加法运算符。
uint16_t i = 0xFFFF;
i = i + (uint16_t)3; /* Truncated as per the warning */
答案 4 :(得分:2)
在许多情况下,直接对小的无符号整数类型执行整数运算会很有用。因为ushort1 = ushort1+intVal;
在所有已定义的情况下的行为等同于将intVal
强制转换为ushort1
的类型,然后直接在该类型上执行添加,因此,标准的作者看到了没有必要为这种情况编写特殊规则。我认为他们清楚地认识到这种行为是有用的,但他们希望无论标准是否强制要求,实施通常都会以这种方式运作。
顺便提一下,当结果被强制转换为uint16_t
时,gcc有时会以不同的方式对类型uint16_t
的值进行算术运算。例如,给定
uint32_t multest1(uint16_t x, uint16_t y)
{
x*=y;
return x;
}
uint32_t multest2(uint16_t x, uint16_t y)
{
return (x*y) & 65535u;
}
在所有情况下,函数multest1()
似乎始终执行乘法mod 65536,但函数multest2却没有。例如,函数:
void tester(uint16_t n, uint16_t *p)
{
n|=0x8000;
for (uint16_t i=0x8000; i<n; i++)
*p++= multest2(65535,i);
return 0;
}
将优化等效于:
void tester(uint16_t n, uint16_t *p)
{
n|=0x8000;
if (n != 0x8000)
*p++= 0x8000;
return 0;
}
但使用multest1
时不会发生这种简化。我不认为gcc的mod-65536行为是可靠的,但代码生成的差异表明:
一些编译器,包括gcc,直接执行mod 65536算法,直接将结果强制转换为uint16_t,但是......
有些编译器以可能导致错误行为的方式处理整数溢出,即使代码完全忽略了结果的所有高位,因此试图警告所有可能的UB的编译器应该标记构造允许哪些编译器以愚蠢的方式处理可移植性违规行为。
虽然ushort1 + = intval形式的许多语句不可能导致溢出,但是更容易在所有这些语句中发出声音,而不仅仅识别那些实际上可能导致错误行为的语句。