type c bit-shift

type - Declarar variables de 64 bits en C



bit type c (5)

Tengo una pregunta.

uint64_t var = 1; // this is 000000...00001 right?

Y en mi código esto funciona:

var ^ (1 << 43)

Pero, ¿cómo sabe que 1 debería estar en 64 bits? ¿No debería escribir esto en su lugar?

var ^ ( (uint64_t) 1 << 43 )


Como suponía, 1 es un int plano simple (que probablemente en su plataforma tiene 32 bit de ancho en aritmética de complemento de 2), y también 43, por lo que 1<<43 da como resultado un desbordamiento: en hechos, si ambos argumentos son de tipo int . las reglas del operador dictan que el resultado será también un int .

Aún así, en C el desbordamiento de enteros es un comportamiento indefinido, por lo que en principio cualquier cosa podría pasar. En su caso, probablemente el compilador emitió el código para realizar ese cambio en un registro de 64 bits, por lo que parece que funciona; para obtener un resultado garantizado correcto, debe usar el segundo formulario que escribió o, como alternativa, especifique 1 como un literal de unsigned long long utiliza el sufijo ull (se garantiza que el unsigned long long es al menos de 64 bits).

var ^ ( 1ULL << 43 )


Recomiendo el enfoque de OP, ( (uint64_t) 1 << 43 ) la constante ( (uint64_t) 1 << 43 )

Para el pequeño ejemplo de OP, los 2 a continuación probablemente realizarán lo mismo.

uint64_t var = 1; // OP solution) var ^ ( (uint64_t) 1 << 43 ) // Others suggested answer var ^ ( 1ULL << 43 )

Los resultados anteriores tienen el mismo valor , pero diferentes tipos . La diferencia de potencial radica en cómo existen 2 tipos en C: uint64_t y unsigned long long y lo que puede seguir.

uint64_t tiene un rango exacto de 0 a 2 64 -1.
unsigned long long tiene un rango de 0 a al menos 2 64 -1.

Si unsigned long long siempre será de 64 bits, ya que parece estar en muchas máquinas en estos días, no hay problema, pero miremos hacia el futuro y digamos que este código se ejecutó en una máquina donde unsigned long long tenía 16 bytes (0 a al menos 2 128 -1).

Un ejemplo artificial a continuación: El primer resultado de ^ es uint64_t , cuando se multiplica por 3, el producto seguirá siendo uint64_t , realizando un módulo 2 64 , si se produce un desbordamiento , entonces el resultado se asigna a d1 . En el siguiente caso, el resultado de ^ es unsigned long long y cuando se multiplica por 3, el producto puede ser más grande que 2 64 que luego se asigna a d2 . Entonces d1 y d2 tienen una respuesta diferente.

double d1, d2; d1 = 3*(var ^ ( (uint64_t) 1 << 43 )); d2 = 3*(var ^ ( 1ULL << 43 ));

Si uno quiere trabajar con unit64_t , sea consistente. No suponga que unit64_t y unsigned long long son iguales. Si está bien que su respuesta sea unsigned long long , bien. Pero en mi experiencia, si uno comienza a usar tipos de tamaño fijo como uint64_t , uno no quiere que los tipos de tamaños de variantes uint64_t los cálculos.


Su compilador no sabe que el cambio debe hacerse en 64 bits. Sin embargo, con esta versión particular del compilador en esta configuración particular para este código en particular, dos errores ocurren para hacer un derecho. No cuentes con eso

Suponiendo que int es un tipo de 32 bits en su plataforma (que es muy probable), los dos errores en 1 << 43 son:

  • Si la cantidad de desplazamiento es mayor o igual que el ancho del tipo del operando de la izquierda, el comportamiento no está definido. Esto significa que si x es de tipo int o unsigned int , entonces x << 43 tiene un comportamiento indefinido, como lo hace x << 32 o cualquier otra x << n donde n ≥ 32. Por ejemplo, 1u << 43 tendría un comportamiento indefinido también.
  • Si el operando izquierdo tiene un tipo firmado, y el resultado de la operación se desborda de ese tipo, entonces el comportamiento no está definido. Por ejemplo 0x12345 << 16 tiene un comportamiento indefinido, porque el tipo del operando izquierdo es el tipo firmado int pero el valor del resultado no cabe en int . Por otro lado, 0x12345u << 16 está bien definido y tiene el valor 0x23450000u .

"Comportamiento no definido" significa que el compilador es libre de generar código que se bloquea o devuelve un resultado incorrecto. Resulta que obtuviste el resultado deseado en este caso, esto no está prohibido, sin embargo, la ley de Murphy dicta que un día el código generado no hará lo que tú quieras.

Para garantizar que la operación tenga lugar en un tipo de 64 bits, debe asegurarse de que el operando de la izquierda sea de 64 bits; no importa el tipo de la variable a la que le está asignando el resultado. Es el mismo problema que float x = 1 / 2 resulta en x contiene 0 y no 0.5: solo los tipos de los operandos son importantes para determinar el comportamiento del operador aritmético. Cualquiera de (uint64)1 << 43 o (long long)1 << 43 o (unsigned long long)1 << 43 o 1ll << 43 o 1ull << 43 lo harán. Si usa un tipo con signo, entonces el comportamiento solo se define si no hay desbordamiento, por lo que si espera el desbordamiento al desbordarse, asegúrese de usar un tipo sin firmar. En general, se recomienda un tipo sin signo incluso si no se supone que ocurre el desbordamiento porque el comportamiento es reproducible: si usa un tipo con signo, el mero hecho de imprimir valores con fines de depuración podría cambiar el comportamiento (porque a los compiladores les gusta aprovechar de comportamiento indefinido para generar el código más eficiente a nivel micro, que puede ser muy sensible a factores como la presión en la asignación de registros).

Como intenta que el resultado sea del tipo uint64_t , es más claro realizar todos los cálculos con ese tipo. Así:

uint64_t var = 1; … var ^ ((uint64_t)1 << 43) …


Una forma portátil de tener una unit64_t constante es usar la macro UINT64_C (de stdint.h ):

UINT64_C(1) << 43

Lo más probable es que UINT64_C(c) esté definido como algo como c ## ULL .

Del estándar C:

La macro INT N _C(value) se expandirá a una expresión entera constante correspondiente al tipo int_least N _t . La macro UINTN_ C (value) se expandirá a una expresión constante entera que corresponde al tipo uint_least N _t . Por ejemplo, si uint_least64_t es un nombre para el tipo unsigned long long int , entonces UINT64_C(0x123) podría expandirse a la constante entera 0x123ULL .


var ^ ( 1ULL << 43 ) debería hacerlo.