salas paginas online nuevo nueva net mexico elchatea c bit-manipulation

paginas - ¿Por qué((char sin signo) 0x80)<< 24 muestra extendida a 0xFFFFFFFF80000000(64-bit)?



salas de chat (5)

El compilador C realiza promociones enteras antes de ejecutar el cambio.

La regla 6.3.1.1 de la norma dice:

Si un int puede representar todos los valores del tipo original, el valor se convierte en un int ; de lo contrario, se convierte a unsigned int . Estas se llaman promociones enteras.

Como todos los valores de unsigned char se pueden representar por int , 0x80 se convierte a int firmado. Lo mismo no es cierto sobre unsigned int : algunos de sus valores no se pueden representar como int , por lo que permanece unsigned int después de aplicar las promociones enteras.

El siguiente programa

#include <inttypes.h> /* printf(" %" PRIu32 "/n"), my_uint32_t) */ #include <stdio.h> /* printf(), perror() */ int main(int argc, char *argv[]) { uint64_t u64 = ((unsigned char)0x80) << 24; printf("%" PRIX64 "/n", u64); /* uint64_t */ u64 = ((unsigned int)0x80) << 24; printf("%016" PRIX64 "/n", u64); }

produce

FFFFFFFF80000000 0000000080000000

¿Cuál es la diferencia entre ((unsigned char)0x80) y ((unsigned int)0x80) en este contexto?

Supongo que (unsigned char)0x80 se promociona a (unsigned char)0xFFFFFFFFFFFFFF80 y luego se desplaza un bit, pero ¿por qué esta conversión cree que se ha (unsigned char)0xFFFFFFFFFFFFFF80 unsigned char ?

También es interesante observar que 0x80 << 16 produce el resultado esperado, 0x0000000000800000 .


El operando izquierdo del operador << somete a una promoción entera.

(C99, 6.5.7p3) "Las promociones enteras se realizan en cada uno de los operandos".

Significa esta expresión:

((unsigned char)0x80) << 24

es equivalente a:

((int) (unsigned char)0x80) << 24

equivalente a:

0x80 << 24

que establece el bit de signo de una int en un sistema int 32 bits. Entonces, cuando 0x80 << 24 se convierte en uint64_t en la declaración u64 la extensión del signo se produce para producir el valor 0xFFFFFFFF80000000 .

EDITAR:

Tenga en cuenta que, como Matt McNabb agregó correctamente en los comentarios, técnicamente 0x80 << 24 invoca un comportamiento indefinido en C ya que el resultado no es representable en el tipo del << operando izquierdo. Si está utilizando gcc , la versión actual del compilador guarantees que no hace esta operación indefinida actualmente.


La parte extraña de la conversión ocurre al convertir el resultado de << de int32 a uint64. Está trabajando en un sistema de 32 bits, por lo que el tamaño del tipo entero es de 32 bits. El siguiente código:

u64 = ((int) 0x80) << 24; printf("%llx/n", u64);

huellas dactilares:

FFFFFFFF80000000

porque (0x80 << 24) da 0x8000000 que es una representación de 32 bits de -2147483648. Este número se convierte a 64 bits al multiplicar el bit de signo y da 0xFFFFFFFF80000000 .


Lo que estás presenciando es un comportamiento indefinido . C99 §6.5.7 / 4 describe el desplazamiento a la izquierda de esta manera:

El resultado de E1 << E2 es E1 posiciones de bit E2 desplazadas a la izquierda; los bits vacíos se rellenan con ceros. Si E1 tiene un tipo sin signo, el valor del resultado es E1 × 2 E2 , módulo reducido uno más que el valor máximo representable en el tipo de resultado. Si E1 tiene un tipo firmado y un valor no negativo, y E1 × 2 E2 es representable en el tipo de resultado, entonces ese es el valor resultante; de lo contrario, el comportamiento no está definido.

En su caso, E1 tiene el valor 128, y su tipo es int , no unsigned char . Como otras respuestas han mencionado, el valor se promueve a int antes de la evaluación. Los operandos involucrados están firmados int , y el valor de 128 desplazados a la izquierda 24 lugares es 2147483648, que es uno más que el valor máximo representable por int en su sistema. Por lo tanto, el comportamiento de su programa no está definido.

Para evitar esto, puedes asegurarte de que el tipo de E1 unsigned int esté unsigned int por conversión de tipo a eso en lugar de a unsigned char .


Una dificultad importante con la evolución del estándar C es que cuando se hicieron esfuerzos para estandarizar el lenguaje, no solo hubo implementaciones que hicieron ciertas cosas de manera diferente entre sí, sino que se creó un cuerpo significativo de código para aquellas implementaciones que confiaba en esas diferencias de comportamiento . Debido a que los creadores del estándar C querían evitar prohibir que las implementaciones se comportaran de manera que los usuarios de esas implementaciones puedan confiar, ciertas partes del estándar C son un verdadero desastre. Algunos de los peores aspectos se refieren a aspectos de la promoción de enteros, como el que ha observado.

Conceptualmente, parecería que tendría más sentido tener unsigned char que promocionar a unsigned int que a signed int , al menos cuando se usa como cualquier cosa que no sea el operando de la derecha del operador - . Las combinaciones de otros operadores pueden producir grandes resultados, pero no hay forma de que otro operador que no sea - podría arrojar un resultado negativo. Para ver por qué se eligió signed int pesar de que el resultado no puede ser negativo, considere lo siguiente:

int i1; unsigned char b1,b2; unsigned int u1; long l1,l2,l3; l1 = i1+u1; l2 = i1+b1; l3 = i1+(b1+b2);

No hay ningún mecanismo en C por el cual una operación entre dos tipos diferentes pueda producir un tipo que no sea uno de los originales, por lo que la primera declaración debe realizar la adición como firmada o no; unsigned generalmente produce resultados ligeramente menos sorprendentes, especialmente dado que los literales enteros están firmados por defecto (sería muy extraño si agregar 1 lugar de 1u a un valor sin signo podría hacerlo negativo). Sin embargo, sería sorprendente que la tercera declaración pueda convertir un valor negativo de i1 en un gran número sin firmar. Tener el primer enunciado anterior arroja un resultado sin firmar, pero el tercer enunciado arroja un resultado firmado que implica que (b1+b2) debe estar firmado.

En mi humilde opinión, la forma "correcta" de resolver los problemas relacionados con la firma sería definir tipos numéricos separados que hayan documentado el comportamiento de "envoltura" (como los tipos sin firmar actuales), y aquellos que deberían comportarse como números enteros, y tienen los dos tipos de tipos exhiben diferentes reglas de promoción. Las implementaciones tendrían que seguir respaldando el comportamiento existente para el código que usa tipos existentes, pero los nuevos tipos podrían implementar reglas que fueron diseñadas para favorecer la usabilidad sobre la compatibilidad.