c++ boolean undefined-behavior evaluation abi

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 ni true ni false

(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; }

becomes

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 tipo TYPE ocuparán N 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 que bool , pero en fortran si los bits son 00000011 es true , 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 .