+ = uint16_t的运算符将指定的值提升为int并且不会编译

imm*_*tal 45 c gcc

这对我来说是一个真正的WTF,看起来像GCC中的一个错误,但我想让社区看一看并为我找到解决方案.

这是我能想到的最简单的程序:

#include <stdio.h>
#include <stdint.h>

int main(void)
{
 uint16_t i = 1;
 uint16_t j = 2;
 i += j;
 return i;
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用-Werror=conversion标志在GCC上编译它,我正在使用我的大部分代码.

这是结果:

.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;
Run Code Online (Sandbox Code Playgroud)

此代码会发生同样的错误:

uint16_t i = 1;
i += ((uint16_t)3);
Run Code Online (Sandbox Code Playgroud)

错误是

.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);
       ^
Run Code Online (Sandbox Code Playgroud)

为了清楚起见,这里的错误在于+=操作员,而不是演员.

它看起来像运算符重载为+=uint16_t被搞砸.或者我错过了一些微妙的东西?

供您使用:MCVE

编辑:更多相同:

.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);
Run Code Online (Sandbox Code Playgroud)

i = (uint16_t)(i +3);至少有效......

dbu*_*ush 33

隐式转换的原因是+=运算符与=和的等价性+.

根据C标准第6.5.16.2节:

3形式E1 op = E2的复合赋值等效于简单赋值表达式E1 = E1 op(E2),除了左值E1仅被评估一次,并且对于不确定序列的函数调用,操作复合赋值是单一评估

所以这:

i += ((uint16_t)3);
Run Code Online (Sandbox Code Playgroud)

相当于:

i = i + ((uint16_t)3);
Run Code Online (Sandbox Code Playgroud)

在此表达式中,运算+符的操作数被提升为int,并将其int分配回a uint16_t.

第6.3.1.1节详细说明了原因:

2以下内容可用于任何地方intunsigned int可能使用的表达方式:

  • 一个整型(比其它的对象或表达intunsigned int),其整数转换秩小于或等于的秩intunsigned int.
  • 类型的位字段_Bool,int,signed int,或unsigned int.

如果a int可以表示原始类型的所有值(由宽度限制,对于位字段),则该值将转换为int; 否则,它被转换为unsigned int.这些被称为 整数促销.整数促销不会更改所有其他类型.

因为a uint16_t(aka a unsigned short int)的排名低于int,所以当用作操作数时,值会被提升+.

您可以通过分解+=操作员并施放右侧来解决这个问题.此外,由于促销,对值3的强制转换无效,因此可以删除:

i =  (uint16_t)(i + 3);
Run Code Online (Sandbox Code Playgroud)

但请注意,此操作会发生溢出,这是没有强制转换时发出警告的原因之一.例如,如果i值为65535,则i + 3类型int和值为65538.当结果返回到时uint16_t,从该值中减去值65536,得到值2,然后将其分配回i.

在这种情况下,此行为已明确定义,因为目标类型是无符号的.如果目标类型已签名,则结果将是实现定义的.

  • 我需要盯着谁才能让这种行为消失.这只是一个古老的错误. (3认同)
  • 谢谢,我自己想到了这一点,我只想了解编译器的来源以及导致此错误的原因?为什么我的uint16_t在分配之前被提升为int? (2认同)

小智 13

任何算术运算符的参数都遵循N1570(最新的C11草案),§6.3.1.8中描述的通常的算术转换.与此问题相关的段落如下:

[关于浮点类型的一些规则]

否则,将对两个操作数执行整数提升.

因此,进一步查看整数促销的定义方式,我们在第6.3.1.1节中找到相关文本:

如果a int可以表示原始类型的所有值(由宽度限制,对于位字段),则该值将转换为int; 否则,它被转换为unsigned int.这些被称为整数促销.

所以,即使使用此代码:

i += ((uint16_t)3);
Run Code Online (Sandbox Code Playgroud)

算术运算符的存在会导致操作数转换回int.作为分配是操作的一部分,它分配一个inti.

这确实是相关的,因为i + 3实际上可能会溢出uint16_t.


Lun*_*din 11

i += ((uint16_t)3);
Run Code Online (Sandbox Code Playgroud)

等于(1)

i = i + ((uint16_t)3);
Run Code Online (Sandbox Code Playgroud)

最右边的操作数显式地从int(整数常量的类型3)转换为强制转换uint16_t.之后,通常的算术转换(2)应用于两个操作数+,之后两个操作数被隐式转换为int.操作的结果+是类型int.

然后,您尝试将存储intuint16_t其中正确导致从一个警告-Wconversion.

如果你想避免分配一个int可能的解决方案uint16_t将是这样的(符合MISRA-C等):

i = (uint16_t)(i + 3u);
Run Code Online (Sandbox Code Playgroud)

(1)这是所有复合赋值算子的要求,C11 6.5.16.2:

化合物分配形式的E1 OP = E2相当于简单赋值表达式E1 = E1 OP(E2),不同之处在于左值E1仅计算一次,

(2)有关隐式类型促销的更多信息,请参阅隐式类型促销规则.