+ = operator for uint16_t facilitates the assignment of an int value and does not compile

This is a real WTF for me, it seems like a bug in GCC, but I would like the community to look and find a solution for me.

Here is the simplest program that I could build:

#include <stdio.h> #include <stdint.h> int main(void) { uint16_t i = 1; uint16_t j = 2; i += j; return i; } 

I am trying to compile this on GCC with the flag -Werror=conversion , which I use for most of my code.

Here is the result:

 .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; 

Same error for this code:

 uint16_t i = 1; i += ((uint16_t)3); 

Mistake

 .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); ^ 

To be clear, the error here is in the += operator, and not in the role.

It seems that the operator overload for += with uint16_t corrupted. Or did I miss something subtle here?

For use: MCVE

Edit: slightly more:

 .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); 

But i = (uint16_t)(i +3); at least it works ...

+43
c gcc
Nov 21 '17 at 15:43
source share
5 answers

The reason for the implicit conversion is due to the equivalence of the operator += c = and + .

From section 6.5.16.2 Standard C :

3 A cumulative assignment of the form E1 op = E2 is equivalent to a simple assignment E1 = E1 op (E2), except that the value lvalue E1 is evaluated only once and with respect to an indefinitely sequenced function call, the compound assignment operation represents a single estimate

So this is:

 i += ((uint16_t)3); 

It is equivalent to:

 i = i + ((uint16_t)3); 

In this expression, the operands of the + operator advance to int and that int assigned back.

Section 6.3.1.1 details the reason for this:

2 The following expressions may be used in the expression: 26> or unsigned int :

  • An object or expression with an integer type (except int or unsigned int ) whose integer conversion rank is less than or equal to the rank of int and unsigned int .
  • Bit field of type _Bool , int , signed int or unsigned int .

If int can represent all values โ€‹โ€‹of the original type (as limited in width for a bit field), the value is converted to an int value; otherwise, it is converted to unsigned int . They are called whole promotions. All other types are not changed by an integer number of shares.

Since a uint16_t (aka an unsigned short int ) has a lower rank than int , the values โ€‹โ€‹increase when used as operands to + .

You can get around this by breaking the += operator and choosing the right side. In addition, due to the advertising act, casting the value 3 does not have an effect, therefore it can be deleted:

 i = (uint16_t)(i + 3); 

Please note, however, that this operation is subject to overflow, which is one reason for warning when there is no casting. For example, if i has a value of 65535, then i + 3 is of type int and has a value of 65538. When the result returns to uint16_t , the value 65536 is subtracted from this value to get a value of 2, which then returns to i .

This behavior is well defined in this case because the type of destination is not specified. If the destination type has been signed, the result will be determined by the implementation.

+32
Nov 21 '17 at 15:53
source share

An argument to any arithmetic operator obeys the usual arithmetic transforms described in N1570 (latest draft C11), ยง6.3.1.8 . The walkthrough related to this issue is as follows:

[some rules about floating point types]

Otherwise, whole promotions run on both operands.

So, studying how whole promotions are determined, we find the corresponding text in ยง6.3.1.1 p2 :

If int can represent all values โ€‹โ€‹of the original type (limited by width, for a bit field), the value is converted to int ; otherwise, it is converted to unsigned int . They are called whole promotions.

So, even with this code:

 i += ((uint16_t)3); 

the presence of an arithmetic operator causes the operand to be converted back to int . Since the assignment is part of the operation, it assigns from int to i .

This is really true, because i + 3 can overflow uint16_t .

+13
Nov 21 '17 at 15:58
source share
 i += ((uint16_t)3); 

equal to (1)

 i = i + ((uint16_t)3); 

The rightmost operand is explicitly converted from int (type of integer constant 3 ) to uint16_t application. After that, the usual arithmetic conversions (2) are applied to both operands + , after which both operands are implicitly converted to int . The result of the + operation is of type int .

Then you try to save the int in uint16_t , which correctly displays a warning from -Wconversion .

A possible workaround if you want to avoid assigning an int to uint16_t would be something like this (MISRA-C compatible, etc.):

 i = (uint16_t)(i + 3u); 



(1) This is defined for all compound assignment operators, C11 6.5.16.2:

A compound assignment of the form E1 op = E2 is equivalent to a simple assignment E1 = E1 > op ( E2 ), except that the lvalue E1 is evaluated only once,

(2) See the Implicit Promotion Type Rules for more information on implicit type advertising campaigns.

+11
Nov 21 '17 at 15:56
source share

An explanation is found here :

joseph [at] codesourcery.com 2009-07-15 14:15:38 UTC
Subject: Re: -Wconversion: do not warn for operands no more than the target type

On Wed, Jul 15, 2009, ian at airs dot com wrote:

> Of course, it can be wrapped, but -Wconversion is not for wrapping warnings.

This is for warnings about implicit conversions that change value; arithmetic, in a wider type (intentionally or otherwise), does not wrap, but the value is changed by implicit conversion back to char. If the user had explicit casts to int in their arithmetic, there could be no doubt that the warning is appropriate.

The warning arises because in the compiler the computer performs arithmetic using a larger type than uint16_t (a int , by int advancement), and placing the value back in uint16_t may truncate it. For example,

 uint16_t i = 0xFFFF; i += (uint16_t)3; /* Truncated as per the warning */ 

The same applies to individual assignment and addition operators.

 uint16_t i = 0xFFFF; i = i + (uint16_t)3; /* Truncated as per the warning */ 
+3
Nov 21 '17 at 15:56
source share

There are many cases where it would be useful to perform integer operations directly on small unsigned integer types. Because the behavior of ushort1 = ushort1+intVal; in all certain cases it will be tantamount to forcing intVal to type ushort1 , and then performing the addition directly on this type, however, the authors of the Standard did not need to write special rules for this situation. I think they clearly realized that this behavior was useful, but they expected implementations to behave as a rule, regardless of whether the standard authorized it.

By the way, gcc sometimes handles arithmetic for values โ€‹โ€‹of type uint16_t differently when the result is forced to uint16_t than when it is not. For example, given

 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; } 

The multest1() function, apparently, in all cases sequentially modifies the 65536 mod, but the multest2 function does not. For example, the function:

 void tester(uint16_t n, uint16_t *p) { n|=0x8000; for (uint16_t i=0x8000; i<n; i++) *p++= multest2(65535,i); return 0; } 

will be optimized equivalently:

 void tester(uint16_t n, uint16_t *p) { n|=0x8000; if (n != 0x8000) *p++= 0x8000; return 0; } 

but this simplification will not happen when using multest1 . I would not consider the behavior of gcc mod-65536 reliable, but the difference in code generation shows that:

  • Some compilers, including gcc, do the mod 65536 arithmetic directly when the result is forcibly applied to uint16_t, but ...

  • Some compilers handle integer overflows in ways that can cause erroneous behavior, even if the code completely ignores all the upper bits of the result, so a compiler that tries to warn about all possible UBs must specify constructs whose compilers are allowed to handle stupidly as mobility violations.

Although there are many operators of the form ushort1 + = intval that cannot cause overflow, it is easier to curse with all such statements than to identify only those that can cause erroneous behavior.

+2
Nov 21 '17 at 16:52
source share



All Articles