stringtranslate.com

Tipo juego de palabras

En informática , un juego de palabras de tipos es cualquier técnica de programación que subvierte o elude el sistema de tipos de un lenguaje de programación para lograr un efecto que sería difícil o imposible de lograr dentro de los límites del lenguaje formal.

En C y C++ , se proporcionan construcciones como la conversión de tipos de puntero y (C++ agrega la conversión de tipos de referencia y a esta lista) para permitir muchos tipos de juegos de palabras, aunque algunos tipos en realidad no son compatibles con el lenguaje estándar.unionreinterpret_cast

En el lenguaje de programación Pascal , el uso de registros con variantes se puede utilizar para tratar un tipo de datos particular de más de una manera, o de una manera normalmente no permitida.

Ejemplo de enchufes

Un ejemplo clásico de juego de palabras se encuentra en la interfaz de sockets de Berkeley . La función para vincular un socket abierto pero no inicializado a una dirección IP se declara de la siguiente manera:

int bind ( int sockfd , struct sockaddr * my_addr , socklen_t addrlen );       

La bindfunción generalmente se llama de la siguiente manera:

estructura sockaddr_in sa = { 0 }; int calcetín = ...; sa . sin_familia = AF_INET ; sa . sin_port = htons ( puerto ); enlazar ( sockfd , ( struct sockaddr * ) & sa , sizeof sa );                

La biblioteca de sockets de Berkeley se basa fundamentalmente en el hecho de que en C , un puntero a struct sockaddr_ines libremente convertible en un puntero a struct sockaddr; y, además, que los dos tipos de estructura compartan la misma disposición de memoria. Por lo tanto, una referencia al campo de estructura my_addr->sin_family(donde my_addres de tipo struct sockaddr*) en realidad se referirá al campo sa.sin_family(donde saes de tipo struct sockaddr_in). En otras palabras, la biblioteca de sockets utiliza juegos de palabras para implementar una forma rudimentaria de polimorfismo o herencia .

A menudo se ve en el mundo de la programación el uso de estructuras de datos "rellenadas" para permitir el almacenamiento de diferentes tipos de valores en lo que efectivamente es el mismo espacio de almacenamiento. Esto se ve a menudo cuando se utilizan dos estructuras en exclusividad mutua para la optimización.

Ejemplo de punto flotante

No todos los ejemplos de juegos de palabras implican estructuras, como lo hizo el ejemplo anterior. Supongamos que queremos determinar si un número de punto flotante es negativo. Podríamos escribir:

bool is_negative ( flotante x ) { return x < 0.0 ; }       

Sin embargo, suponiendo que las comparaciones de punto flotante sean costosas, y suponiendo también que floatse representen de acuerdo con el estándar de punto flotante IEEE y que los números enteros tengan 32 bits de ancho, podríamos utilizar juegos de palabras para extraer el bit de signo del número de punto flotante. usando solo operaciones con números enteros:

bool es_negativo ( flotante x ) { int * i = ( int * ) & x ; devolver * yo < 0 ; }            

Tenga en cuenta que el comportamiento no será exactamente el mismo: en el caso especial de xser cero negativo , la primera implementación produce falsemientras que la segunda produce true. Además, la primera implementación devolverá falsecualquier valor de NaN , pero la última podría devolver truevalores de NaN con el bit de signo establecido.

Este tipo de juego de palabras es más peligroso que la mayoría. Mientras que el primer ejemplo se basaba únicamente en garantías hechas por el lenguaje de programación C sobre el diseño de la estructura y la convertibilidad del puntero, el último ejemplo se basa en suposiciones sobre el hardware de un sistema en particular. Algunas situaciones, como código en el que el tiempo es crítico y que de otro modo el compilador no podría optimizar , pueden requerir código peligroso. En estos casos, documentar todas estas suposiciones en comentarios e introducir aserciones estáticas para verificar las expectativas de portabilidad ayuda a mantener el código mantenible .

Ejemplos prácticos de juegos de palabras con punto flotante incluyen la raíz cuadrada inversa rápida popularizada por Quake III , la comparación rápida de FP como números enteros, [1] y la búsqueda de valores vecinos incrementando como un número entero (implementando nextafter). [2]

Por idioma

C y C++

Además de la suposición sobre la representación de bits de números de punto flotante, el ejemplo anterior de juego de palabras con punto flotante también viola las restricciones del lenguaje C sobre cómo se accede a los objetos: [3] el tipo declarado de xes floatpero se lee a través de un expresión de tipo unsigned int. En muchas plataformas comunes, este uso de juegos de palabras con punteros puede crear problemas si diferentes punteros se alinean de maneras específicas de la máquina . Además, los punteros de diferentes tamaños pueden crear alias en los accesos a la misma memoria , provocando problemas que el compilador no controla. Sin embargo, incluso cuando el tamaño de los datos y la representación del puntero coinciden, los compiladores pueden confiar en las restricciones de no alias para realizar optimizaciones que serían inseguras en presencia de alias no permitidos.

uso de punteros

Se puede lograr un intento ingenuo de hacer juegos de palabras mediante el uso de punteros: (El siguiente ejemplo en ejecución asume la representación de bits IEEE-754 para el tipo float).

bool is_negative ( float x ) { int32_t i = * ( int32_t * ) & x ; // En C++ esto es equivalente a: int32_t i = *reinterpret_cast<int32_t*>(&x); devolver yo < 0 ; }            


Las reglas de alias del estándar C establecen que solo se podrá acceder a un objeto a su valor almacenado mediante una expresión lvalue de un tipo compatible. [4] Los tipos floaty int32_tno son compatibles, por lo tanto el comportamiento de este código no está definido . Aunque en GCC y LLVM este programa en particular se compila y ejecuta como se esperaba, ejemplos más complicados pueden interactuar con suposiciones hechas por alias estrictos y conducir a comportamientos no deseados. La opción -fno-strict-aliasinggarantizará el comportamiento correcto del código utilizando esta forma de juego de palabras, aunque se recomienda utilizar otras formas de juego de palabras. [5]

Uso deunion

En C, pero no en C++, a veces es posible realizar juegos de palabras mediante un archivo union.

bool es_negativo ( flotante x ) { unión { int i ; flotar d ; } mi_unión ; mi_union . re = x ; devolver mi_unión . yo < 0 ; }                  

Acceder my_union.idespués de escribir más recientemente al otro miembro, my_union.des una forma permitida de juego de palabras en C, [6] siempre que el miembro leído no sea mayor que aquel cuyo valor se estableció (de lo contrario, la lectura tiene un comportamiento no especificado [7] ). Lo mismo es sintácticamente válido pero tiene un comportamiento indefinido en C++, [8] sin embargo, donde solo se considera que el último miembro escrito de a uniontiene algún valor.

Para ver otro ejemplo de juego de palabras con tipos, consulte Zancada de una matriz .

Uso debit_cast

En C++20 , el std::bit_castoperador permite juegos de palabras sin comportamiento indefinido. También permite etiquetar la función constexpr.

constexpr bool is_negative ( float x ) noexcept { static_assert ( std :: numeric_limits < float >:: is_iec559 ); // (habilitar solo en IEEE 754) auto i = std :: bit_cast < std :: int32_t > ( x ); devolver yo < 0 ; }                

Pascal

Un registro de variante permite tratar un tipo de datos como múltiples tipos de datos dependiendo de a qué variante se hace referencia. En el siguiente ejemplo, se supone que el número entero es de 16 bits, mientras que el entero largo y el real son 32, mientras que se supone que el carácter es de 8 bits:

tipo VariantRecord = registro caso RecType : LongInt de 1 : ( I : matriz [ 1 .. 2 ] de Entero ) ; (* no se muestra aquí: puede haber varias variables en la declaración de caso de un registro variante *) 2 : ( L : LongInt ) ; 3 : ( R : Real ) ; 4 : ( C : matriz [ 1 .. 4 ] de Char ) ; fin ;                                 var V : Registro Variante ; K : Entero ; LA : Int Largo ; RA : Real ; Ch : Personaje ;               V. _ Yo [ 1 ] := 1 ; Ch := V . C [ 1 ] ; (* esto extraería el primer byte de VI *) V . R := 8,3 ; LA := V . L ; (* esto almacenaría un Real en un Entero *)           

En Pascal, copiar un real a un número entero lo convierte al valor truncado. Este método traduciría el valor binario del número de punto flotante a lo que sea como un entero largo (32 bits), que no será el mismo y puede ser incompatible con el valor entero largo en algunos sistemas.

Estos ejemplos podrían usarse para crear conversiones extrañas, aunque, en algunos casos, puede haber usos legítimos para este tipo de construcciones, como para determinar ubicaciones de datos específicos. En el siguiente ejemplo, se supone que un puntero y un entero largo son de 32 bits:

escriba PA = ^ Arec ;    Arec = caso de registro RT : LongInt de 1 : ( P : PA ) ; 2 : ( L : Int Largo ) ; fin ;                 varPP : PA ; _ K : Int Largo ;      Nuevo ( PP ) ; PP ^. P := PP ; WriteLn ( 'La variable PP se encuentra en la dirección' , Hex ( PP ^. L )) ;   

Donde "nuevo" es la rutina estándar en Pascal para asignar memoria para un puntero, y "hex" es presumiblemente una rutina para imprimir la cadena hexadecimal que describe el valor de un número entero. Esto permitiría mostrar la dirección de un puntero, algo que normalmente no está permitido. (Los punteros no se pueden leer ni escribir, solo asignarse). Asignar un valor a una variante entera de un puntero permitiría examinar o escribir en cualquier ubicación de la memoria del sistema:

PP ^. L := 0 ; PP := PP ^. PAG ; (*PP ahora apunta a la dirección 0*) K := PP ^. L ; (*K contiene el valor de la palabra 0*) WriteLn ( 'La palabra 0 de esta máquina contiene' , K ) ;         

Esta construcción puede provocar una verificación del programa o una violación de la protección si la dirección 0 está protegida contra la lectura en la máquina en la que se ejecuta el programa o en el sistema operativo bajo el que se ejecuta.

La técnica de reinterpretación de C/C++ también funciona en Pascal. Esto puede resultar útil cuando, por ejemplo. leyendo dwords de un flujo de bytes y queremos tratarlos como flotantes. Aquí hay un ejemplo práctico, donde reinterpretamos y lanzamos una palabra dword a un flotante:

escriba pReal = ^ Real ;   var DW : DPalabra ; F : Real ;      F := pReal ( @ DW ) ^;  

C#

En C# (y otros lenguajes .NET), los juegos de palabras con tipos son un poco más difíciles de lograr debido al sistema de tipos, pero de todos modos se pueden hacer usando punteros o uniones de estructuras.

Consejos

C# sólo permite punteros a los llamados tipos nativos, es decir, cualquier tipo primitivo (excepto string), enumeración, matriz o estructura que esté compuesto únicamente por otros tipos nativos. Tenga en cuenta que los punteros sólo se permiten en bloques de código marcados como "inseguros".

flotador pi = 3,14159 ; uint piAsRawData = * ( uint * ) & pi ;      

Uniones estructurales

Se permiten uniones estructurales sin ninguna noción de código "inseguro", pero requieren la definición de un nuevo tipo.

[StructLayout(LayoutKind.Explicit)] struct FloatAndUIntUnion { [FieldOffset(0)] flotante público DataAsFloat ;      [FieldOffset(0)] público uint DataAsUInt ; }   //...Unión FloatAndUIntUnion ; Unión . Datos como flotador = 3,14159 ; uint piAsRawData = unión . DatosAsUInt ;      

Código CIL sin formato

Se puede utilizar CIL sin formato en lugar de C#, porque no tiene la mayoría de las limitaciones de tipo. Esto permite, por ejemplo, combinar dos valores de enumeración de un tipo genérico:

TEnum a = ...; TEnum b = ...; TEnum combinado = a | b ; // ilegal            

Esto puede evitarse mediante el siguiente código CIL:

. método público estático hidebysig !! TEnum CombineEnums < tipo de valor . ctor ([ mscorlib ] System . ValueType ) TEnum > ( !! TEnum a , !! TEnum b ) cil administrado { . pila máxima 2                  ldarg . 0 ldarg . 1 o // esto no provocará un desbordamiento, porque a y b tienen el mismo tipo y, por lo tanto, el mismo tamaño. retirarse }     

El cpblkcódigo de operación CIL permite algunos otros trucos, como convertir una estructura en una matriz de bytes:

. método público estático hidebysig uint8 [] ToByteArray < tipo de valor . ctor ([ mscorlib ] System . ValueType ) T > ( !! T & v // 'ref T' en C# ) cil administrado { . inicio local ( [0] uint8 [] )                     . pila máxima 3  // crea una nueva matriz de bytes con longitud sizeof(T) y la almacena en el local 0 sizeof !! T newarr uint8 dup // guarda una copia en la pila para más adelante (1) stloc . 0        PMA . i4 . 0 ldelema uint8   // memcpy(local 0, &v, tamaño de(T)); // <la matriz todavía está en la pila, consulte (1)> ldarg . 0 // esta es la *dirección* de 'v', porque su tipo es '!!T&' sizeof !! T cpblk       ldloc . 0 retirado } 

Referencias

  1. ^ Herf, Michael (diciembre de 2001). "trucos de base". estereopsis: gráficos .
  2. ^ "Trucos estúpidos de flotación". ASCII aleatorio: blog de tecnología de Bruce Dawson . 24 de enero de 2012.
  3. ^ ISO/IEC 9899:1999 s6.5/7
  4. ^ "§ 6.5/7" (PDF) , ISO/IEC 9899:2018 , 2018, pág. 55, archivado desde el original (PDF) el 30 de diciembre de 2018. Solo se podrá acceder a su valor almacenado de un objeto mediante una expresión lvalue que tenga uno de los siguientes tipos: [...]
  5. ^ "Errores del CCG - Proyecto GNU". gcc.gnu.org .
  6. ^ "§ 6.5.2.3/3, nota a pie de página 97" (PDF) , ISO/IEC 9899:2018 , 2018, pág. 59, archivado desde el original (PDF) el 30 de diciembre de 2018. Si el miembro utilizado para leer el contenido de un objeto de unión no es el mismo que el miembro utilizado por última vez para almacenar un valor en el objeto, la parte apropiada del La representación de objeto del valor se reinterpreta como una representación de objeto en el nuevo tipo como se describe en 6.2.6 ( un proceso a veces denominado "juego de palabras de tipos" ). Esta podría ser una representación de trampa.
  7. ^ "§ J.1/1, viñeta 11" (PDF) , ISO/IEC 9899:2018 , 2018, p. 403, archivado desde el original (PDF) el 30 de diciembre de 2018. No se especifica lo siguiente: … Los valores de bytes que corresponden a miembros del sindicato distintos del último almacenado en (6.2.6.1).
  8. ^ ISO/IEC 14882:2011 Sección 9.5

enlaces externos