He visto que este patrón se usa mucho en C y C ++.
unsigned int flags = -1; // all bits are true
¿Es esta una buena forma portátil de lograr esto? ¿O está usando 0xffffffff
o ~0
mejor?
fuente
He visto que este patrón se usa mucho en C y C ++.
unsigned int flags = -1; // all bits are true
¿Es esta una buena forma portátil de lograr esto? ¿O está usando 0xffffffff
o ~0
mejor?
Le recomiendo que lo haga exactamente como lo ha mostrado, ya que es el más sencillo. Inicialice a -1
lo que funcionará siempre , independientemente de la representación de signo real, mientras ~
que a veces tendrá un comportamiento sorprendente porque tendrá que tener el tipo de operando correcto. Solo así obtendrá el valor más alto de un unsigned
tipo.
Para un ejemplo de una posible sorpresa, considere este:
unsigned long a = ~0u;
No almacenará necesariamente un patrón con todos los bits 1 en a
. Pero primero creará un patrón con todos los bits 1 en un unsigned int
, y luego lo asignará a a
. Lo que sucede cuando unsigned long
tiene más bits es que no todos son 1.
Y considere este, que fallará en una representación de complemento de no dos:
unsigned int a = ~0; // Should have done ~0u !
La razón de esto es que ~0
tiene que invertir todos los bits. Invirtiendo que producirá -1
en la máquina de complemento a dos (que es el valor que necesitamos!), Sino que no se dió -1
en otra representación. En la máquina de complemento de uno, produce cero. Por lo tanto, en la máquina de complemento de uno, lo anterior se inicializará a
a cero.
Lo que debe entender es que se trata de valores, no de bits. La variable se inicializa con un valor . Si en el inicializador modifica los bits de la variable utilizada para la inicialización, el valor se generará de acuerdo con esos bits. El valor que necesita para inicializar a
al valor más alto posible es -1
o UINT_MAX
. El segundo dependerá del tipo de a
- necesitará usar ULONG_MAX
para un unsigned long
. Sin embargo, el primero no dependerá de su tipo, y es una buena manera de obtener el mayor valor.
Estamos no hablando de si -1
tiene todos los bits de uno (que no siempre tiene). Y estamos no hablar acerca de si ~0
tiene todos los bits (uno que tiene, por supuesto).
Pero de lo que estamos hablando es de cuál es el resultado de la flags
variable inicializada . Y para ello, solo-1
funcionará con todo tipo y máquina.
numeric_limits<size_t>::max()
es un poco largo aliento, pero también lo es el elenco ...
-1
está representado, ni pregunta qué bits ~0
tiene. Puede que no nos interesen los valores, pero al compilador sí. No podemos ignorar el hecho de que las operaciones funcionan con y por valores. El valor de ~0
puede no ser -1
, pero este es el valor que necesita. Vea mi respuesta y el resumen de @ Dingo.
unsigned int flags = -1;
Es portátil.unsigned int flags = ~0;
no es portátil porque se basa en una representación de complemento a dos.unsigned int flags = 0xffffffff;
no es portátil porque supone entradas de 32 bits.Si desea establecer todos los bits de una manera garantizada por el estándar C, use el primero.
~0
produce un int
valor con todos los bits establecidos, por supuesto. Pero asignar un int
an unsigned int
no necesariamente da como resultado que el int sin signo tenga el mismo patrón de bits que el patrón de bits con signo. Solo con una representación de complemento a 2 es siempre este el caso. En un complemento de 1s o representación de magnitud de signo, la asignación de un int
valor negativo a un unsigned int
resultado en un patrón de bits diferente. Esto se debe a que el estándar C ++ define la conversión con signo -> sin signo para que sea el valor de módulo igual, no el valor con los mismos bits.
Francamente, creo que todos los fff son más legibles. En cuanto al comentario de que es un antipatrón, si realmente le importa que todos los bits estén configurados / borrados, diría que probablemente se encuentre en una situación en la que le importa el tamaño de la variable de todos modos, lo que requeriría algo como impulso :: uint16_t, etc.
Una forma de evitar los problemas mencionados es simplemente hacer:
unsigned int flags = 0;
flags = ~flags;
Portátil y al punto.
flags
como const
.
unsigned int const flags = ~0u;
~0
es un número entero que tiene todos los bits establecidos en 1, pero cuando lo asigna int
a la unsigned
variable flags
, realiza una conversión de valor de -2**31
(suponiendo un 32 bits int
) a (-2**31 % 2**32) == 2**31
, que es un número entero con todos los bits excepto el primer set en 1.
u
sufijo en tu respuesta. Eso, por supuesto, funcionaría, pero aún tiene el problema de especificar el tipo de datos que usa ( unsigned
y no más grande) dos veces, lo que podría provocar errores. Sin embargo, es más probable que aparezca el error si la asignación y la declaración de variable inicial están más separadas.
No estoy seguro de usar un unsigned int para banderas es una buena idea en primer lugar en C ++. ¿Qué pasa con bitset y similares?
std::numeric_limit<unsigned int>::max()
es mejor porque 0xffffffff
supone que unsigned int es un entero de 32 bits.
auto
. auto const flags = std::numeric_limit<unsigned>::max()
.
unsigned int flags = -1; // all bits are true
"¿Es esta una buena forma portátil de lograr esto?"
¿Portátil? Sí .
¿Bueno? Debatible , como lo demuestra toda la confusión que se muestra en este hilo. Ser lo suficientemente claro para que sus compañeros programadores puedan entender el código sin confusión debería ser una de las dimensiones que medimos para un buen código.
Además, este método es propenso a las advertencias del compilador . Para eludir la advertencia sin paralizar su compilador, necesitaría un reparto explícito. Por ejemplo,
unsigned int flags = static_cast<unsigned int>(-1);
El lanzamiento explícito requiere que prestes atención al tipo de destino. Si presta atención al tipo de objetivo, entonces, naturalmente, evitará las trampas de los otros enfoques.
Mi consejo sería prestar atención al tipo de destino y asegurarse de que no haya conversiones implícitas. Por ejemplo:
unsigned int flags1 = UINT_MAX;
unsigned int flags2 = ~static_cast<unsigned int>(0);
unsigned long flags3 = ULONG_MAX;
unsigned long flags4 = ~static_cast<unsigned long>(0);
Todo lo cual es correcto y más obvio para sus compañeros programadores.
Y con C ++ 11 : podemos usar auto
para simplificar cualquiera de estos:
auto flags1 = UINT_MAX;
auto flags2 = ~static_cast<unsigned int>(0);
auto flags3 = ULONG_MAX;
auto flags4 = ~static_cast<unsigned long>(0);
Considero correcto y obvio mejor que simplemente correcto.
La conversión de -1 en cualquier tipo sin signo está garantizada por el estándar para dar como resultado todos. El uso de ~0U
es generalmente malo ya que 0
tiene tipo unsigned int
y no llenará todos los bits de un tipo sin signo más grande, a menos que escriba explícitamente algo como ~0ULL
. En sistemas sanos, ~0
debería ser idéntico a -1
, pero dado que el estándar permite representaciones de complemento y signo / magnitud, estrictamente hablando no es portátil.
Por supuesto, siempre está bien escribir 0xffffffff
si sabe que necesita exactamente 32 bits, pero -1 tiene la ventaja de que funcionará en cualquier contexto, incluso si no conoce el tamaño del tipo, como las macros que funcionan en varios tipos , o si el tamaño del tipo varía según la implementación. Si lo hace saber el tipo, otra forma segura de obtener todos unos macros es el límite UINT_MAX
, ULONG_MAX
, ULLONG_MAX
, etc.
Personalmente siempre uso -1. Siempre funciona y no tienes que pensarlo.
~(type)0
(bueno, complete el derecho, type
por supuesto). Lanzar cero todavía da como resultado un cero, por lo que está claro, y negar todos los bits en el tipo de destino está claramente definido. Sin embargo, no es tan frecuente que realmente quiera esa operación; YMMV.
neg
instrucción. Las máquinas que tienen un comportamiento aritmético con signo falso tienen códigos opcionales aritméticos con signo / sin signo. Por supuesto, un compilador realmente bueno ignoraría siempre los códigos de operación firmados incluso para los valores firmados y, por lo tanto, obtendría dos complementos de forma gratuita.
var = ~(0*var)
caso fallará por var
ser un tipo sin signo más estrecho que int
. Tal vez var = ~(0U*var)
? (Personalmente, todavía prefiero -1
, sin embargo).
Siempre que tenga #include <limits.h>
uno de sus incluidos, solo debe usar
unsigned int flags = UINT_MAX;
Si quieres un poco de bits, puedes usar
unsigned long flags = ULONG_MAX;
Se garantiza que estos valores tienen todos los bits de valor del resultado establecidos en 1, independientemente de cómo se implementen los enteros con signo.
Si. Como se menciona en otras respuestas, -1
es el más portátil; sin embargo, no es muy semántico y activa advertencias del compilador.
Para resolver estos problemas, pruebe este simple ayudante:
static const struct All1s
{
template<typename UnsignedType>
inline operator UnsignedType(void) const
{
static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
return static_cast<UnsignedType>(-1);
}
} ALL_BITS_TRUE;
Uso:
unsigned a = ALL_BITS_TRUE;
uint8_t b = ALL_BITS_TRUE;
uint16_t c = ALL_BITS_TRUE;
uint32_t d = ALL_BITS_TRUE;
uint64_t e = ALL_BITS_TRUE;
ALL_BITS_TRUE ^ a
donde a
está un entero con signo? El tipo permanece entero con signo y el patrón de bits (representación de objeto) depende de que el objetivo sea el complemento de 2 o no.
ALL_BITS_TRUE ^ a
da un error de compilación porque ALL_BITS_TRUE
es ambiguo. Podría ser utilizado como uint32_t(ALL_BITS_TRUE) ^ a
, sin embargo. Puede probarlo usted mismo en cpp.sh :) Hoy en día agregaría un static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
en el operator
para asegurarse de que los usuarios no intenten usarlo int(ALL_BITS_TRUE)
. Actualizaré la respuesta.
No haría la cosa -1. Es bastante no intuitivo (al menos para mí). Asignar datos firmados a una variable sin firmar simplemente parece ser una violación del orden natural de las cosas.
En tu situación, siempre uso 0xFFFF
. (Utilice el número correcto de Fs para el tamaño variable, por supuesto).
[Por cierto, rara vez veo el truco -1 hecho en el código del mundo real.]
Además, si realmente se preocupan por los bits individuales en una vairable, sería una buena idea para empezar a utilizar el ancho fijo uint8_t
, uint16_t
, uint32_t
tipos.
En los procesadores Intel IA-32 está bien escribir 0xFFFFFFFF en un registro de 64 bits y obtener los resultados esperados. Esto se debe a que IA32e (la extensión de 64 bits a IA32) solo admite elementos inmediatos de 32 bits. En las instrucciones de 64 bits, los inmediatos de 32 bits se extienden por signos a 64 bits.
Lo siguiente es ilegal:
mov rax, 0ffffffffffffffffh
Lo siguiente pone 64 1s en RAX:
mov rax, 0ffffffffh
Solo para completar, lo siguiente coloca 32 1s en la parte inferior de RAX (también conocido como EAX):
mov eax, 0ffffffffh
Y, de hecho, he tenido errores en los programas cuando quería escribir 0xffffffff en una variable de 64 bits y obtuve un 0xffffffffffffffff en su lugar. En C esto sería:
uint64_t x;
x = UINT64_C(0xffffffff)
printf("x is %"PRIx64"\n", x);
el resultado es:
x is 0xffffffffffffffff
Pensé en publicar esto como un comentario a todas las respuestas que decían que 0xFFFFFFFF asume 32 bits, pero tanta gente lo respondió que pensé que lo agregaría como una respuesta separada.
UINT64_C(0xffffffff)
expande a algo así 0xffffffffuLL
, definitivamente es un error del compilador. El estándar C analiza en gran medida los valores , el valor representado por 0xffffffff
es 4294967295 (no 36893488147419103231), y no hay conversiones a tipos enteros con signo a la vista.
Vea la respuesta de litb para una explicación muy clara de los problemas.
Mi desacuerdo es que, estrictamente hablando, no hay garantías para ninguno de los casos. No conozco ninguna arquitectura que no represente un valor sin signo de 'uno menos que dos a la potencia del número de bits' como todos los bits configurados, pero esto es lo que el Estándar realmente dice (3.9.1 / 7 más nota 44):
Las representaciones de tipos integrales definirán valores mediante el uso de un sistema de numeración binaria puro. [Nota 44:] Una representación posicional para enteros que usa los dígitos binarios 0 y 1, en los que los valores representados por bits sucesivos son aditivos, comienzan con 1 y se multiplican por la potencia integral sucesiva de 2, excepto quizás por el bit con La posición más alta.
Eso deja la posibilidad de que uno de los bits sea cualquier cosa.
Aunque el 0xFFFF
(o 0xFFFFFFFF
, etc.) puede ser más fácil de leer, puede romper la portabilidad del código que de otro modo sería portátil. Considere, por ejemplo, una rutina de biblioteca para contar cuántos elementos en una estructura de datos tienen determinados bits establecidos (los bits exactos especificados por la persona que llama). La rutina puede ser totalmente agnóstica en cuanto a lo que representan los bits, pero aún debe tener una constante de "todos los bits establecidos". En tal caso, -1 será mucho mejor que una constante hexadecimal ya que funcionará con cualquier tamaño de bit.
La otra posibilidad, si typedef
se usa un valor para la máscara de bits, sería usar ~ (bitMaskType) 0; Si la máscara de bits es solo un tipo de 16 bits, esa expresión solo tendrá 16 bits establecidos (incluso si 'int' sería de otro modo 32 bits) pero dado que 16 bits serán todo lo que se requiere, las cosas deberían estar bien siempre que en realidad usa el tipo apropiado en el typecast.
Por cierto, las expresiones de la forma longvar &= ~[hex_constant]
tienen un problema desagradable si la constante hexadecimal es demasiado grande para caber en un int
, pero encajará en un unsigned int
. Si an int
es de 16 bits, entonces longvar &= ~0x4000;
o longvar &= ~0x10000
; borrará un bit de longvar
, pero longvar &= ~0x8000;
borrará el bit 15 y todos los bits por encima de eso. Los valores que encajan int
tendrán el operador del complemento aplicado a un tipo int
, pero el resultado se extenderá a long
, configurando los bits superiores. Los valores que son demasiado grandes unsigned int
tendrán el operador del complemento aplicado al tipo long
. Sin embargo, los valores que se encuentran entre esos tamaños aplicarán el operador de complemento al tipo unsigned int
, que luego se convertirá a tipo long
sin extensión de signo.
Prácticamente: si
Teóricamente: no.
-1 = 0xFFFFFFFF (o cualquier tamaño que tenga un int en su plataforma) solo es cierto con la aritmética del complemento a dos. En la práctica, funcionará, pero hay máquinas heredadas (mainframes de IBM, etc.) donde tienes un bit de signo real en lugar de una representación de complemento a dos. Su solución ~ 0 propuesta debería funcionar en todas partes.
Como otros han mencionado, -1 es la forma correcta de crear un número entero que se convertirá en un tipo sin signo con todos los bits establecidos en 1. Sin embargo, lo más importante en C ++ es usar los tipos correctos. Por lo tanto, la respuesta correcta a su problema (que incluye la respuesta a la pregunta que hizo) es esta:
std::bitset<32> const flags(-1);
Esto siempre contendrá la cantidad exacta de bits que necesita. Construye un std::bitset
con todos los bits establecidos en 1 por las mismas razones mencionadas en otras respuestas.
Ciertamente es seguro, ya que -1 siempre tendrá todos los bits disponibles, pero me gusta ~ 0 mejor. -1 simplemente no tiene mucho sentido para un unsigned int
. 0xFF
... no es bueno porque depende del ancho del tipo.
Yo digo:
int x;
memset(&x, 0xFF, sizeof(int));
Esto siempre te dará el resultado deseado.
Aprovechar el hecho de que asignar todos los bits a uno para un tipo sin signo es equivalente a tomar el valor máximo posible para el tipo dado
y extender el alcance de la pregunta a todos los tipos enteros sin signo:
Asignar -1 funciona para cualquier tipo de entero sin signo (unsigned int, uint8_t, uint16_t, etc.) para C y C ++.
Como alternativa, para C ++, puede:
<limits>
y usarstd::numeric_limits< your_type >::max()
El propósito podría ser agregar más claridad, ya que la asignación -1
siempre necesitaría algún comentario explicativo.
Una forma de hacer que el significado sea un poco más obvio y, sin embargo, evitar repetir el tipo:
const auto flags = static_cast<unsigned int>(-1);
Sí, la representación mostrada es muy correcta, ya que si lo hacemos al revés, requerirá que un operador invierta todos los bits, pero en este caso la lógica es bastante sencilla si consideramos el tamaño de los enteros en la máquina
Por ejemplo, en la mayoría de las máquinas, un número entero es de 2 bytes = el valor máximo de 16 bits que puede contener es 2 ^ 16-1 = 65535 2 ^ 16 = 65536
0% 65536 = 0 -1% 65536 = 65535 que corresponde a 1111 ............. 1 y todos los bits se establecen en 1 (si consideramos las clases de residuos mod 65536) por lo tanto es mucho sencillo
supongo
no, si considera esta noción, es perfectamente ideal para entradas sin signo y realmente funciona
solo verifique el siguiente fragmento de programa
int main () {
unsigned int a=2;
cout<<(unsigned int)pow(double(a),double(sizeof(a)*8));
unsigned int b=-1;
cout<<"\n"<<b;
getchar();
return 0;
}
respuesta para b = 4294967295 que es -1% 2 ^ 32 en enteros de 4 bytes
por lo tanto, es perfectamente válido para enteros sin signo
en caso de discrepancias, por favor informe
-1
siempre funcionará, el hecho de que se necesite un comentario después de que demuestre que no es un código claro. Si la variable está destinada a ser una colección de banderas, ¿por qué asignarle un número entero? Su tipo puede ser un entero, pero ciertamente no es semánticamente un entero. Nunca lo vas a incrementar o multiplicar. Por lo tanto, no lo usaría0xffffffff
para portabilidad o corrección, sino para mayor claridad.-1
sigue siendo una solución portátil y compatible con versiones anteriores para ambos idiomas, pero podría afectar parte del razonamiento en otras respuestas.