c++ - Establecer bits extra en un bool lo hace verdadero y falso al mismo tiempo
boolean undefined-behavior (2)
En C ++, la representación de bits (e incluso el tamaño) de un
bool
está definida por la implementación;
en general, se implementa como un tipo de caracteres tomados 1 o 0 como valores posibles.
Si establece su valor en algo diferente de los permitidos (en este caso específico al aliasing
bool
través de un
char
y modificando su representación de bits), está rompiendo las reglas del lenguaje, por lo que cualquier cosa puede suceder.
En particular, se especifica explícitamente en el estándar que un
bool
"roto" puede comportarse como
true
y
false
(o ni
true
ni
false
) al mismo tiempo:
El uso de un valor
bool
en las formas descritas por esta Norma Internacional como "indefinido", como al examinar el valor de un objeto automático sin inicializar, puede hacer que se comporte como si no fuera nitrue
nifalse
(C ++ 11, [basic.fundamental], nota 47)
En este caso particular,
puedes ver cómo terminó en esta extraña situación
: el primero
if
se compila para
movzx eax, BYTE PTR [rbp-33]
test al, al
je .L22
que carga
T
en
eax
(con extensión cero) y omite la impresión si todo es cero;
el siguiente si en cambio es
movzx eax, BYTE PTR [rbp-33]
xor eax, 1
test al, al
je .L23
La prueba
if(T == false)
se transforma a
if(T^1)
, que voltea solo el bit bajo.
Esto estaría bien para un
bool
válido, pero para su "roto" no lo corta.
Tenga en cuenta que esta secuencia extraña solo se genera en niveles de optimización bajos;
en los niveles más altos, esto generalmente se reducirá a una comprobación de cero / distinto de cero, y es probable que una secuencia como la suya se convierta en
una única prueba / rama condicional
.
De todos modos, obtendrá un comportamiento extraño en otros contextos, por ejemplo, al sumar valores
bool
a otros enteros:
int foo(bool b, int i) {
return i + b;
}
foo(bool, int):
movzx edi, dil
lea eax, [rdi+rsi]
ret
donde
dil
es "de confianza" para ser 0/1.
Si su programa es todo C ++, entonces la solución es simple: no rompa los valores
bool
esta manera, evite alterar su representación de bits y todo irá bien;
en particular, incluso si asigna de un entero a un
bool
el compilador emitirá el código necesario para asegurarse de que el valor resultante sea un
bool
válido, por lo que su
bool T = 3
es realmente seguro, y
T
terminará con un
true
en sus entrañas.
Si, por el contrario, necesita interoperar con el código escrito en otros idiomas que pueden no compartir la misma idea de lo que es un
bool
, simplemente evite el código de "límite", y hágalo una lista como un número entero del tamaño adecuado.
Funcionará en condicionales y co.
tan bien
Actualización sobre el lado de Fortran / interoperabilidad del problema
Descargo de responsabilidad: todo lo que sé sobre Fortran es lo que leí esta mañana en documentos estándar, y que tengo algunas tarjetas perforadas con listas de Fortran que uso como marcadores, así que no lo dude.
En primer lugar, este tipo de cosas de interoperabilidad de lenguaje no es parte de los estándares de lenguaje, sino de la plataforma ABI. Como estamos hablando de Linux x86-64, el documento relevante es el System V x86-64 ABI .
En primer lugar, en ninguna parte se especifica que el tipo C
_Bool
(que se define como el mismo que C ++
bool
en 3.1.2 nota †) tiene algún tipo de compatibilidad con Fortran
LOGICAL
;
en particular, en 9.2.2, la tabla 9.2 especifica que "lógico"
LOGICAL
se asigna a
signed int
.
Sobre los tipos
TYPE*N
dice que
La notación "
TYPE*N
" especifica que las variables o los miembros agregados de tipoTYPE
ocuparánN
bytes de almacenamiento.
(ibídem.)
No hay un tipo equivalente explícitamente especificado para
LOGICAL*1
, y es comprensible: ni siquiera es estándar;
de hecho, si intenta compilar un programa Fortran que contenga un
LOGICAL*1
en modo compatible con Fortran 95, recibirá advertencias al respecto, tanto por ifort
./example.f90(2): warning #6916: Fortran 95 does not allow this length specification. [1]
logical*1, intent(in) :: x
------------^
y por gfort
./example.f90:2:13:
logical*1, intent(in) :: x
1
Error: GNU Extension: Nonstandard type declaration LOGICAL*1 at (1)
así las aguas ya están embarradas;
Entonces, combinando las dos reglas anteriores, me gustaría que
signed char
estuviera a salvo.
Sin embargo : el ABI también especifica:
Los valores para el tipo
.TRUE.
son.TRUE.
Implementado como 1 y.FALSE.
implementado como 0.
Entonces, si tiene un programa que almacena cualquier cosa además de 1 y 0 en un valor
LOGICAL
,
¡ya está fuera de especificaciones en el lado de Fortran
!
Tu dices:
Un
logical*1
fortran tiene la misma representación quebool
, pero en fortran si los bits son 00000011 estrue
, en C ++ no está definido.
Esta última afirmación no es cierta, el estándar de Fortran es agnóstico a la representación, y la ABI dice explícitamente lo contrario. De hecho, puede ver esto en acción fácilmente al verificar la salida de gfort para la comparación LÓGICA :
integer function logical_compare(x, y)
logical, intent(in) :: x
logical, intent(in) :: y
if (x .eqv. y) then
logical_compare = 12
else
logical_compare = 24
end if
end function logical_compare
se convierte en
logical_compare_:
mov eax, DWORD PTR [rsi]
mov edx, 24
cmp DWORD PTR [rdi], eax
mov eax, 12
cmovne eax, edx
ret
Notará que hay un
cmp
recto entre los dos valores, sin normalizarlos primero (a diferencia de
ifort
, eso es más conservador a este respecto).
Aún más interesante: independientemente de lo que diga el ABI, ifort por defecto utiliza una representación no estándar para
LOGICAL
;
Esto se explica en la documentación del conmutador
-fpscomp logicals
, que también especifica algunos detalles interesantes sobre la
-fpscomp logicals
y entre idiomas:
Especifica que los enteros con un valor distinto de cero se tratan como verdaderos, los enteros con un valor cero se tratan como falsos. La constante literal .TRUE. tiene un valor entero de 1 y la constante literal .FALSE. tiene un valor entero de 0. Esta representación es utilizada por las versiones de Intel Fortran anteriores a la Versión 8.0 y por Fortran PowerStation.
El valor predeterminado es
fpscomp nologicals
, que especifica que los valores enteros impares (bit uno bajo) se tratan como verdaderos e incluso los valores enteros (bit cero bajo) se tratan como falsos.La constante literal .TRUE. tiene un valor entero de -1, y la constante literal .FALSE. tiene un valor entero de 0. Esta representación es utilizada por Compaq Visual Fortran. La representación interna de los valores LÓGICOS no está especificada por el estándar de Fortran. Los programas que utilizan valores enteros en contextos LÓGICOS, o que pasan valores LÓGICOS a procedimientos escritos en otros idiomas, no son portátiles y pueden no ejecutarse correctamente. Intel recomienda que evite las prácticas de codificación que dependen de la representación interna de los valores lógicos.
(énfasis añadido)
Ahora, la representación interna de un
LOGICAL
normalmente no debería ser un problema, ya que, de lo que recojo, si juegas "según las reglas" y no cruzas los límites del idioma, no lo notarás.
Para un programa compatible estándar, no hay una "conversión directa" entre
INTEGER
y
LOGICAL
;
La única forma en que veo que puede meter un
INTEGER
en un
LOGICAL
parece ser
TRANSFER
, que es intrínsecamente no portátil y no ofrece garantías reales, o la conversión no estándar
INTEGER
<->
LOGICAL
en la asignación.
El último
está documentado
por gfort para siempre resultar en un valor distinto de cero ->
.TRUE.
, cero ->
.FALSE.
, y
puede ver
que en todos los casos se genera un código para que esto suceda (aunque sea un código complicado en el caso de ifort con la representación heredada), por lo que parece que no puede empujar un número entero arbitrario en una
LOGICAL
de esta manera.
logical*1 function integer_to_logical(x)
integer, intent(in) :: x
integer_to_logical = x
return
end function integer_to_logical
integer_to_logical_:
mov eax, DWORD PTR [rdi]
test eax, eax
setne al
ret
La conversión inversa para un
LOGICAL*1
es una extensión cero recta (gfort), por lo que, para cumplir el contrato en la documentación que se encuentra en la parte superior, es evidente que se espera que el valor
LOGICAL
sea 0 o 1.
Pero en general, la situación de estas conversiones es un poco desordenada , así que me mantendría alejado de ellas.
Por lo tanto, para abreviar, evite poner los datos
INTEGER
en valores
LOGICAL
, ya que son malos incluso en Fortran, y asegúrese de usar la marca de compilación correcta para obtener la representación compatible con ABI para los booleanos, y la interoperabilidad con C / C ++ debería ser correcta .
Pero para ser más seguro, solo usaría el
char
simple en el lado de C ++.
Finalmente, a partir de lo que obtengo de la documentación , en ifort hay un soporte integrado para la interoperabilidad con C, incluidos los booleanos; Usted puede tratar de aprovecharlo.
Si obtengo una variable
bool
y establezco su segundo bit en 1, la variable se evalúa como verdadera y falsa al mismo tiempo.
Compile el siguiente código con gcc6.3 con la opción
-g
, (
gcc-v6.3.0/Linux/RHEL6.0-2016-x86_64/bin/g++ -g main.cpp -o mytest_d
) y ejecute el archivo ejecutable.
Obtienes lo siguiente.
¿Cómo puede T ser igual a verdadero y falso al mismo tiempo?
value bits
----- ----
T: 1 0001
after bit change
T: 3 0011
T is true
T is false
Esto puede suceder cuando llama a una función en un idioma diferente (por ejemplo, fortran) donde la definición verdadera y falsa es diferente de C ++. Para fortran si algún bit no es 0, entonces el valor es verdadero, si todos los bits son cero, entonces el valor es falso.
#include <iostream>
#include <bitset>
using namespace std;
void set_bits_to_1(void* val){
char *x = static_cast<char *>(val);
for (int i = 0; i<2; i++ ){
*x |= (1UL << i);
}
}
int main(int argc,char *argv[])
{
bool T = 3;
cout <<" value bits " <<endl;
cout <<" ----- ---- " <<endl;
cout <<" T: "<< T <<" "<< bitset<4>(T)<<endl;
set_bits_to_1(&T);
bitset<4> bit_T = bitset<4>(T);
cout <<"after bit change"<<endl;
cout <<" T: "<< T <<" "<< bit_T<<endl;
if (T ){
cout <<"T is true" <<endl;
}
if ( T == false){
cout <<"T is false" <<endl;
}
}
/////////////////////////////////// // Función de Fortran que no es compatible con C ++ cuando se compila con ifort.
logical*1 function return_true()
implicit none
return_true = 1;
end function return_true
Esto es lo que sucede cuando viola su contrato tanto con el lenguaje como con el compilador.
Probablemente escuchaste en alguna parte que "cero es falso" y "no cero es verdadero".
Eso se mantiene cuando se adhiere a los parámetros del idioma, convirtiendo estáticamente un
int
a
bool
o viceversa.
No se mantiene cuando empiezas a jugar con representaciones de bits. En ese caso, rompe su contrato e ingresa en el ámbito del comportamiento definido por la implementación (como mínimo).
Simplemente no hagas eso.
No depende de usted cómo se almacena un
bool
en la memoria.
Depende del compilador.
Si desea cambiar el valor de un
bool
, asigne
true
/
false
, o asigne un número entero y use los mecanismos de conversión adecuados proporcionados por C ++.
El estándar de C ++ usado para dar una respuesta específica a cómo usar
bool
de esta manera es malo, malo y malvado (
"Usar un valor
bool
en las formas descritas en este documento como" indefinidas ", como al examinar el valor de un un objeto automático sin inicializar, podría hacer que se comporte como si no fuera ni
true
ni
false
".
), aunque se
eliminó en C ++ 20 por razones editoriales
.