what uint8_t uint16_t uint16 c gcc

c - uint16_t - uint8_t use



+=operador para uint16_t promueve el valor asignado a int y no compilará (5)

El argumento para cualquier operador aritmético está sujeto a las conversiones aritméticas habituales descritas en N1570 (último borrador C11), §6.3.1.8 . El pasaje relevante para esta pregunta es el siguiente:

[algunas reglas sobre tipos de puntos flotantes]

De lo contrario, las promociones enteras se realizan en ambos operandos.

Entonces, mirando más allá de cómo se definen las promociones enteras , encontramos el texto relevante en §6.3.1.1 p2 :

Si un int puede representar todos los valores del tipo original (como está restringido por el ancho, para un campo de bit), el valor se convierte en un int ; de lo contrario, se convierte a unsigned int . Estas se llaman promociones enteras .

Entonces, incluso con este código:

i += ((uint16_t)3);

la presencia de un operador aritmético hace que el operando se convierta nuevamente en int . Como la tarea es parte de la operación, asigna un int a i .

Esto es realmente relevante porque i + 3 podría desbordar un uint16_t .

Esta es una verdadera WTF para mí, parece un error en GCC, pero me gustaría que la comunidad eche un vistazo y encuentre una solución para mí.

Este es el programa más simple que pude reunir:

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

Estoy intentando compilar esto en GCC con -Werror=conversion indicador de -Werror=conversion , que estoy usando para gran parte de mi código.

Este es el resultado:

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

El mismo error sucedería con este código:

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

El error es

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

Para que quede claro, el error aquí está en el operador += , NO en el elenco.

Parece que la sobrecarga del operador para el += con uint16_t está en mal estado. ¿O me estoy perdiendo algo sutil aquí?

Para su uso: MCVE

Editar: Algunos más de lo mismo:

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

Pero i = (uint16_t)(i +3); al menos funciona ...


El motivo de la conversión implícita se debe a la equivalencia del operador += con = y + .

De la sección 6.5.16.2 del estándar C :

3 Una asignación compuesta de la forma E1 op = E2 es equivalente a la expresión de asignación simple E1 = E1 op (E2), excepto que el valor l E1 se evalúa solo una vez, y con respecto a una llamada de función de secuencia indeterminada, la operación de una asignación compuesta es una evaluación única

Así que esto:

i += ((uint16_t)3);

Es equivalente a:

i = i + ((uint16_t)3);

En esta expresión, los operandos del operador + se promueven a int , y ese int se asigna de nuevo a uint16_t .

La sección 6.3.1.1 detalla el motivo de esto:

2 Se puede usar lo siguiente en una expresión donde se pueda usar una int o unsigned int :

  • Un objeto o expresión con un tipo entero (distinto de int o unsigned int ) cuyo rango de conversión entero es menor o igual que el rango de int y unsigned int .
  • Un campo de bit de tipo _Bool , int , signed int o unsigned int .

Si un int puede representar todos los valores del tipo original (como está restringido por el ancho, para un campo de bit), el valor se convierte en un int ; de lo contrario, se convierte a unsigned int . Estas se llaman promociones enteras . Todos los otros tipos no se modifican por las promociones enteras.

Como uint16_t (también conocido como unsigned short int ) tiene un rango inferior que int , los valores se promueven cuando se usan como operandos a + .

Puede evitar esto dividiendo el operador += y lanzando el lado derecho. Además, debido a la promoción, el lanzamiento en el valor 3 no tiene ningún efecto, por lo que puede eliminarse:

i = (uint16_t)(i + 3);

Sin embargo, tenga en cuenta que esta operación está sujeta a un desbordamiento, que es una de las razones por las que se da una advertencia cuando no hay conversión. Por ejemplo, si tengo el valor 65535, entonces i + 3 tiene el tipo int y el valor 65538. Cuando el resultado se devuelve a uint16_t , se resta el valor 65536 de este valor que produce el valor 2, que luego se vuelve a asignar a i .

Este comportamiento está bien definido en este caso porque el tipo de destino no está firmado. Si el tipo de destino se firmó, el resultado sería la implementación definida.


Hay muchos casos en los que sería útil realizar operaciones de enteros directamente en pequeños tipos de enteros sin signo. Porque el comportamiento de ushort1 = ushort1+intVal; en todos los casos definidos será equivalente a forzar intVal al tipo de ushort1 y luego realizar la adición directamente en ese tipo, sin embargo, los autores del Estándar no vieron la necesidad de escribir reglas especiales para esa situación. Creo que ellos claramente reconocieron que tal comportamiento era útil, pero que esperaban que las implementaciones en general se comportarían de esa manera, ya sea que el Estándar lo ordenara o no.

A propósito, gcc a veces procesa la aritmética en los valores de tipo uint16_t de diferentes maneras cuando el resultado se uint16_t a uint16_t que en los casos en que no lo es. Por ejemplo, dado

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

La función multest1() parece realizar consistentemente el mod de multiplicación 65536 en todos los casos, pero la función multest2 no lo hace. Por ejemplo, la función:

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

será optimizado equivalente a:

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

pero tal simplificación no ocurrirá cuando se usa multest1 . No consideraría el comportamiento de gcc mod-65536 como confiable, pero la diferencia en la generación de código muestra que:

  1. Algunos compiladores, incluido gcc, realizan la aritmética mod 65536 directamente cuando se fuerza un resultado directamente a uint16_t, pero ...

  2. Algunos compiladores procesan el desbordamiento de enteros de forma que pueden causar un comportamiento erróneo incluso si el código ignora por completo todos los bits superiores del resultado, por lo que un compilador que intenta advertir de todos los posibles UB debería marcar compilaciones que los compiladores podrían procesar de forma tonta. violaciones de portabilidad.

Si bien hay muchas declaraciones de la forma ushort1 + = intval que posiblemente no podrían causar desbordamiento, es más fácil graznar en todas esas declaraciones que identificar solo las que podrían causar un comportamiento erróneo.


Una explicación se encuentra here :

joseph [at] codesourcery.com 2009-07-15 14:15:38 UTC
Asunto: Re: -Wconversion: no avise para operandos que no sean más grandes que el tipo de destino

El miércoles 15 de julio de 2009, ian at airs dot com escribió:

> Claro, puede envolver, pero -La conversión no es para envolver las advertencias.

Es para advertencias sobre conversiones implícitas que cambian un valor; la aritmética, en un tipo más amplio (deliberadamente o no), no se ajusta, pero el valor se cambia por la conversión implícita de nuevo a char. Si el usuario tiene conversiones explícitas a int en su aritmética, no hay duda de que la advertencia es apropiada.

La advertencia se produce porque el compilador tiene la computadora realiza la aritmética usando un tipo más grande que uint16_t (una int , a través de la promoción entera), y colocando el valor de nuevo en un uint16_t podría uint16_t . Por ejemplo,

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

Lo mismo se aplica a los operadores de asignación y adición separados.

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


i += ((uint16_t)3);

es igual a (1)

i = i + ((uint16_t)3);

El operando del uint16_t derecho se convierte explícitamente de int (el tipo de la constante entera 3 ) a uint16_t por el molde. Después de eso, las conversiones aritméticas habituales (2) se aplican en ambos operandos de + , después de lo cual ambos operandos se convierten implícitamente a int . El resultado de la operación + es de tipo int .

A continuación, intenta almacenar un int en un uint16_t que da como resultado correctamente una advertencia de -Wconversion .

Una posible uint16_t si no desea asignar un int a un uint16_t sería algo como esto (MISRA-C cumple con los requisitos):

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

(1) Esto es obligatorio para todos los operadores de asignación compuesta, C11 6.5.16.2:

Una asignación compuesta de la forma E1 op = E2 es equivalente a la expresión de asignación simple E1 = E1 op ( E2 ), excepto que el lvalue E1 se evalúa solo una vez,

(2) Consulte Reglas de promoción de tipo implícito para obtener más información sobre las promociones de tipo implícito.