Tengo un código como el siguiente bloque de código (no puedo publicar el código original) dentro de un archivo .cpp que creo que está siendo compilado por clang++ (Ubuntu clang version 3.5.2-3ubuntu1 (tags/RELEASE_352/final) (based on LLVM 3.5.2)).
Parece el código C, porque estamos usando GoogleTest para probar nuestro código C. De todos modos:

size_t const SHIFT = 4;
uint8_t var, var2;
/* Omitted: Code that sets var to, say 00011000 (base 2) */
var2 = var;
var = var << SHIFT >> SHIFT; // [1] result is 00011000 (base 2) (tested with printf)
var2 = var2 << SHIFT;
var2 = var2 >> SHIFT; // [2] result is 00001000 (base 2) (tested with printf)

Ahora, ¿por qué el comentario [1] es cierto? Supuse que la línea correspondiente daría como resultado que los 4 bits superiores se pusieran a cero. Pero descubrí que esto no es cierto; el programa simplemente restaura el valor original.

¿Es este un comportamiento definido por el lenguaje, o está clang compilando un cambio de bits supuestamente inútil?

(Verifiqué la asociatividad (usando esta tabla en cppreference.com, asumiendo que la asociatividad / La precedencia de los operadores básicos no diferirá entre las versiones de C++ y probablemente tampoco entre C++ y C, al menos no en las 'versiones actuales') y parece que la expresión RHS en [1] realmente debería producir el mismo resultado que las dos siguientes declaraciones)

7
polynomial_donut 14 feb. 2018 a las 00:13

2 respuestas

La mejor respuesta

Lo que está viendo es el resultado de promociones de números enteros . Cualquier valor con un tipo de rango inferior a int, cuando se usa en una expresión, se promueve a int.

Esto se detalla en la sección 6.3.1.1 del estándar C

2 Lo siguiente puede usarse en una expresión siempre que un int o unsigned int pueda ser usado:

  • Un objeto o expresión con un tipo de número entero (que no sea int o unsigned int) cuya clasificación de conversión de números enteros es menor o igual que la clasificación de int y unsigned int.
  • Un campo de bits de tipo _Bool, int, signed int o unsigned int.

Si un int puede representar todos los valores del tipo original (según lo restringido por el ancho, para un campo de bits), el valor se convierte en un int; de lo contrario, se convierte en unsigned int. Se denominan promociones de números enteros . Todos los demás tipos no se modifican por el promociones enteras.

En este caso:

var = var << SHIFT >> SHIFT;

var primero asciende a int. Este tipo tiene al menos 16 bits de ancho y probablemente 32 bits de ancho. Entonces, el valor con el que se opera es 0x00000018. Un desplazamiento a la izquierda de 4 da como resultado 0x00000180 y, posteriormente, un desplazamiento a la derecha da como resultado 0x00000018.

El resultado luego se almacena en un uint_8. Dado que el valor encaja en una variable de este tipo, no se requiere conversión y se almacena 0x18.

11
dbush 13 feb. 2018 a las 21:30

La expresion:

var = var << SHIFT >> SHIFT;

No es semánticamente equivalente a

var = var << SHIFT ;
var = var >> SHIFT ; 

Eso requeriría:

var = (uint8_t)(var << SHIFT) >> SHIFT ;

Lo que ilustra la diferencia efectiva entre los dos: el comportamiento observado no requiere optimización, sino que es requerido por la definición del lenguaje con respecto a las reglas de promoción de tipos en expresiones.

Dicho esto, también es muy posible que un compilador pueda optimizar los cambios. La optimización no puede cambiar el resultado cuando el comportamiento está definido como en este caso.

1
Clifford 14 feb. 2018 a las 00:03