Ordleksskrivning

Den aktuella versionen av sidan har ännu inte granskats av erfarna bidragsgivare och kan skilja sig väsentligt från versionen som granskades den 19 oktober 2017; kontroller kräver 11 redigeringar .

Typpunning är en term  som används inom datavetenskap för att hänvisa till olika tekniker för att kränka eller lura typsystemet i ett programmeringsspråk , med en effekt som skulle vara svår eller omöjlig att tillhandahålla inom ett formellt språk .

C- och C++-språken tillhandahåller explicita skrivordlekar genom konstruktioner som casts , unionoch även reinterpret_castför C++ , även om standarderna för dessa språk behandlar vissa fall av sådana ordlekar som odefinierat beteende .

I Pascal kan variantnotationer användas för att tolka en viss datatyp på mer än ett sätt, eller till och med på ett sätt som inte är naturligt för språket.

Att skriva ordlek är ett direkt brott mot typsäkerheten . Traditionellt är förmågan att bygga en skrivordlek associerad med svag skrivningunsafe , men vissa starkt skrivna språk eller deras implementeringar tillhandahåller sådana möjligheter (vanligtvis genom att använda orden eller i deras tillhörande identifierare unchecked). Förespråkare av typsäkerhet hävdar att " nödvändigheten " av att skriva ordlekar är en myt [1] .

Exempel: sockets

Ett klassiskt exempel på en skrivordlek kan ses i Berkeleys socket -gränssnitt . En funktion som binder en öppen oinitierad socket till en IP-adress har följande signatur:

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

Funktionen bindbrukar kallas så här:

struct sockaddr_insa = { 0 } ; int sockfd = ...; sa . sin_family = AF_INET ; sa . sin_port = htons ( port ); bind ( sockfd , ( struktur sockaddr * ) & sa , sizeof sa );

Berkeley Sockets Library bygger i grund och botten på det faktum att i C kan en pekare till struct sockaddr_inenkelt konverteras till en pekare till struct sockaddr, och att de två strukturtyperna överlappar varandra i deras minnesorganisation . Därför kommer en pekare till ett fält my_addr->sin_family(där my_addrhar typ struct sockaddr* ) faktiskt att peka på ett fält sa.sin_family(där sahar typ struct sockaddr_in ). Med andra ord använder biblioteket en skrivordlek för att implementera en primitiv form av arv . [2]

Inom programmering är det vanligt att använda strukturer - "lager" som gör att du effektivt kan lagra olika typer av data i ett enda minnesblock . Oftast används detta trick för ömsesidigt uteslutande data i syfte att optimera .

Exempel: flyttalsnummer

Anta att du vill kontrollera att ett flyttal är negativt. Man skulle kunna skriva:

bool är_negativ ( float x ) { return x < 0,0 ; }

Flyttalsjämförelser är dock resurskrävande eftersom de fungerar på ett speciellt sätt för NaN . Med tanke på att typen floatär representerad enligt IEEE 754-2008-standarden , och typen intär 32 bitar lång och har samma teckenbit som i , kan du använda en skrivordlek för att extrahera teckenbiten för ett flyttal med enbart heltal jämförelse: float

bool är_negativ ( float x ) { return * (( int * ) & x ) < 0 ; }

Denna form av att skriva ordlek är den farligaste. Det föregående exemplet förlitade sig endast på de garantier som ges av C - språket angående strukturrepresentation och pekarkonverterbarhet ; Detta exempel är dock baserat på specifika hårdvaruantaganden . I vissa fall, som när man utvecklar realtidsapplikationer som kompilatorn inte kan optimera på egen hand, visar sig sådana farliga programmeringsbeslut vara nödvändiga. I sådana fall hjälper kommentarer och kompileringskontroller ( Static_assertions ) till att säkerställa kodunderhållbarhet . 

Ett verkligt exempel kan hittas i Quake III -koden - se Fast Inverse Square Root .

Förutom antagandena om den bitvisa representationen av flyttal, bryter exemplet ovan på en skrivordlek också mot reglerna som fastställts av C-språket för att komma åt objekt [3] : xdet deklareras som float, men dess värde läses i en uttryck som har typ signed int . På många vanliga plattformar kan den här pekarens ordlek leda till problem om pekarna är olika inriktade i minnet . Dessutom kan pekare av olika storlekar dela samma minnesplatser , vilket leder till fel som inte kan upptäckas av kompilatorn .

Använda union

Problemet med aliasing kan lösas genom att använda union(även om exemplet nedan är baserat på antagandet att flyttalstalet representeras av IEEE-754- standarden ):

bool är_negativ ( float x ) { union { osignerad int ui ; flyta d ; } my_union = { . d = x }; return ( my_union . ui & 0x80000000 ) != 0 ; }

Detta är C99 -kod som använder Designated initialisers .  När en union skapas initieras dess verkliga fält, och sedan läses värdet av hela fältet (fysiskt beläget på samma adress i minnet) enligt klausul s6.5 i standarden. Vissa kompilatorer stöder sådana konstruktioner som en språktillägg, såsom GCC [4] .

För ett annat exempel på en skrivordlek, se Stride of an array   .

Pascal

Variantnotation låter dig överväga datatypen olika sätt, beroende på den angivna varianten. Följande exempel antar integer16 bitar longintoch real32 bitar och character8 bitar:

typ variant_record = postfall rec_type : longint av 1 : ( I : array [ 1..2 ] av heltal ) ; _ _ _ 2 : ( L : longint ) ; 3 : ( R : verklig ) ; 4 : ( C : array [ 1 .. 4 ] tecken ) ; _ slut ; Var V : Variant_record ; K : Heltal ; L.A .: Longint ; RA : Verklig ; Ch : tecken ; ... V . I := 1 ; Ch := V . C [ 1 ] ; (* Hämta den första byten i VI-fältet *) V . R := 8,3 ; LA := V . L ; (* Lagra reellt tal i heltalscell *)

I Pascal konverterar kopiering av ett reellt till ett heltal det till ett avrundat värde. Denna metod konverterar emellertid ett binärt flyttalsvärde till något som har längden av ett långt heltal (32 bitar), vilket inte är identiskt och kan till och med vara inkompatibelt med långa heltal på vissa plattformar.

Sådana exempel kan användas för konstiga transformationer, men i vissa fall kan sådana konstruktioner vara vettiga, till exempel att beräkna platsen för vissa datastycken. Följande exempel antar att pekaren och det långa heltal är 32 bitar:

Typ PA = ^ Arec ; Arec = rekordfall rt : longint av 1 : ( P : PA ) ; _ 2 : ( L : Longint ) ; slut ; Var PP : PA ; K : Longint ; ... Nytt ( PP ) ; P.P. ^. P := PP ; Writeln ( 'Variabeln PP finns i minnet vid' , hex ( PP ^. L )) ;

Standardproceduren Newi Pascal är avsedd att dynamiskt allokera minne för en pekare, och hexantyds av någon procedur som skriver ut en hexadecimal sträng som beskriver värdet på ett heltal. Detta gör att du kan visa adressen till pekaren, vilket vanligtvis är förbjudet (pekare i Pascal kan inte läsas eller matas ut - endast tilldelas). Genom att tilldela ett värde till en heltalsvariant av en pekare kan du läsa och ändra vilket område som helst i systemminnet:

P.P. ^. L : = 0 PP := PP ^. P ; (* PP pekar på adress 0 *) K := PP ^. L ; (*K innehåller värdet av ordet på adress 0 *) Writeln ( 'Ordet på adress 0 på denna maskin innehåller' , K ) ;

Detta program kan fungera korrekt eller krascha om adress 0 är lässkyddad, beroende på operativsystem.

Se även

Anteckningar

  1. Lawrence C. Paulson. ML för den arbetande programmeraren. — 2:a. - Cambridge, Storbritannien: Cambridge University Press, 1996. - S. 2. - 492 sid. - ISBN 0-521-57050-6 (inbunden), 0-521-56543-X (mjuk pärm).
  2. struct sockaddr_in, struct in_addr . www.gta.ufrj.br. Tillträdesdatum: 17 januari 2016. Arkiverad från originalet 24 januari 2016.
  3. ISO/IEC 9899:1999 s6.5/7
  4. GCC: Icke-buggar . Hämtad 21 november 2014. Arkiverad från originalet 22 november 2014.

Länkar