Typepunning er et udtryk , der bruges i datalogi til at henvise til forskellige teknikker til at krænke eller snyde typesystemet i et programmeringssprog , med en effekt, der ville være svær eller umulig at give i et formelt sprog .
C- og C++-sprogene giver eksplicitte skriveordspil gennem konstruktioner som casts , unionog også reinterpret_castfor C++ , selvom standarderne for disse sprog behandler nogle tilfælde af sådanne ordspil som udefineret adfærd .
I Pascal kan variantnotationer bruges til at fortolke en bestemt datatype på mere end én måde, eller endda på en måde, der ikke er hjemmehørende i sproget.
At skrive ordspil er en direkte overtrædelse af typesikkerheden . Traditionelt er evnen til at opbygge et skriveordspil forbundet med svag indtastningunsafe , men nogle stærkt indtastede sprog eller deres implementeringer giver sådanne muligheder (normalt ved at bruge ordene eller i deres tilknyttede identifikatorer unchecked). Tilhængere af typesikkerhed hævder, at " nødvendigheden " af at skrive ordspil er en myte [1] .
Et klassisk eksempel på et skriveordspil kan ses i Berkeley-socket -grænsefladen . En funktion , der binder en åben ikke-initialiseret socket til en IP-adresse , har følgende signatur:
int bind ( int sockfd , struct sockaddr * my_addr , socklen_t addrlen );Funktionen bindkaldes normalt sådan:
struct sockaddr_insa = { 0 } ; int sockfd = ...; sa . sin_familie = AF_INET ; sa . sin_port = htons ( port ); bind ( sockfd , ( struktur sockaddr * ) & sa , sizeof sa );Berkeley Sockets Library baserer sig dybest set på det faktum, at i C kan en pointer til struct sockaddr_inlet konverteres til en pointer til struct sockaddr, og at de to strukturtyper overlapper hinanden i deres hukommelsesorganisation . Derfor vil en markør til et felt my_addr->sin_family(hvor my_addrhar type struct sockaddr* ) faktisk pege på et felt sa.sin_family(hvor sahar type struct sockaddr_in ). Med andre ord bruger biblioteket et skriveordspil til at implementere en primitiv form for arv . [2]
I programmering er det almindeligt at bruge strukturer - "lag", der giver dig mulighed for effektivt at gemme forskellige typer data i en enkelt hukommelsesblok . Oftest bruges dette trick til gensidigt udelukkende data med henblik på optimering .
Antag, at du vil kontrollere, at et flydende kommatal er negativt. Man kunne skrive:
bool er_negativ ( float x ) { returnere x < 0,0 ; }Flydende-komma-sammenligninger er imidlertid ressourcekrævende, fordi de fungerer på en særlig måde for NaN . I betragtning af at typen floater repræsenteret i henhold til IEEE 754-2008 standarden , og typen inter 32 bit lang og har samme fortegnsbit som i , kan du bruge et indtastningsordspil til at udtrække fortegnsbitten af et flydende kommatal ved kun at bruge heltal sammenligning: float
bool er_negativ ( float x ) { return * (( int * ) & x ) < 0 ; }Denne form for at skrive ordspil er den farligste. Det foregående eksempel var kun baseret på garantierne givet af C-sproget med hensyn til strukturrepræsentation og pointer - konverterbarhed ; Dette eksempel er dog baseret på specifikke hardwareantagelser . I nogle tilfælde, såsom ved udvikling af realtidsapplikationer , som compileren ikke er i stand til at optimere på egen hånd, viser det sig, at sådanne farlige programmeringsbeslutninger er nødvendige. I sådanne tilfælde hjælper kommentarer og kompileringstidstjek ( Static_assertions ) med at sikre kodevedligeholdelse .
Et rigtigt eksempel kan findes i Quake III -koden - se Fast Inverse Square Root .
Ud over antagelserne om den bitvise repræsentation af flydende kommatal, overtræder ovenstående eksempel på et skriveordspil også reglerne fastsat af C-sproget for adgang til objekter [3] : xdet erklæres som float, men dets værdi læses i en udtryk, der har type signed int . På mange almindelige platforme kan dette ordspil med markørindtastning føre til problemer, hvis pointerne er forskelligt justeret i hukommelsen . Desuden kan pointere af forskellig størrelse dele de samme hukommelsesplaceringer , hvilket fører til fejl , der ikke kan opdages af compileren .
Problemet med aliasing kan løses ved at bruge union(selvom eksemplet nedenfor er baseret på den antagelse, at det flydende decimaltal er repræsenteret af IEEE-754 standarden ):
bool er_negativ ( float x ) { union { usigneret int ui ; flyde d ; } min_union = { . d = x }; return ( my_union . ui & 0x80000000 ) != 0 ; }Dette er C99 -kode, der bruger Designated initialisers . Når en union oprettes , initialiseres dens rigtige felt, og derefter læses værdien af hele feltet (fysisk placeret på den samme adresse i hukommelsen) i henhold til paragraf s6.5 i standarden. Nogle compilere understøtter sådanne konstruktioner som en sprogudvidelse, såsom GCC [4] .
For et andet eksempel på et skriveordspil, se Stride of an array .
Variantnotation giver dig mulighed for at overveje datatypen på forskellige måder, afhængigt af den angivne variant. Følgende eksempel antager integer16 bit longintog real32 bit og character8 bit:
type variant_record = record case rec_type : longint af 1 : ( I : array [ 1..2 ] af heltal ) ; _ _ 2 : ( L : longint ) ; 3 : ( R : ægte ) ; 4 : ( C : array [ 1 .. 4 ] tegn ) ; _ ende ; Var V : Variant_record ; K : Heltal ; L.A .: Longint ; RA : Virkelig ; Ch : karakter ; ... V . I := 1 ; Ch := V . C [ 1 ] ; (* Hent den første byte i VI-feltet *) V . R := 8,3 ; LA := V . L ; (* Gem reelt tal i heltalscelle *)I Pascal konverterer kopiering af et reelt tal til et heltal det til en afrundet værdi. Denne metode konverterer imidlertid en binær flydende kommaværdi til noget, der har længden af et langt heltal (32 bit), som ikke er identisk og måske endda er inkompatibelt med lange heltal på nogle platforme.
Sådanne eksempler kan bruges til mærkelige transformationer, men i nogle tilfælde kan sådanne konstruktioner give mening, for eksempel ved at beregne placeringen af bestemte datastykker. Følgende eksempel antager, at markøren og det lange heltal er 32 bit:
Type PA = ^ Arec ; Arec = record case rt : længde på 1 : ( P : PA ) ; 2 : ( L : Longint ) ; ende ; Var PP : PA ; K : Longint ; ... Nyt ( PP ) ; P.P. ^. P := PP ; Writeln ( 'Variablen PP er placeret i hukommelsen ved' , hex ( PP ^. L )) ;Standardproceduren Newi Pascal er beregnet til dynamisk at allokere hukommelse til en pointer, og hexer underforstået af en procedure, der udskriver en hexadecimal streng, der beskriver værdien af et heltal. Dette giver dig mulighed for at få vist adressen på markøren, hvilket normalt er forbudt (pointere i Pascal kan ikke læses eller udlæses - kun tildeles). At tildele en værdi til en heltalsvariant af en pointer giver dig mulighed for at læse og ændre ethvert område af systemhukommelsen:
P.P. ^. L : = 0 PP := PP ^. P ; (* PP peger på adresse 0 *) K := PP ^. L ; (*K indeholder værdien af ordet på adresse 0 *) Writeln ( 'Ordet på adresse 0 på denne maskine indeholder' , K ) ;Dette program kan fungere korrekt eller gå ned, hvis adresse 0 er læsebeskyttet, afhængigt af operativsystemet.