A típusfejtés egy olyan kifejezés , amelyet a számítástechnikában használnak a programozási nyelv típusrendszerének megsértésére vagy csalására szolgáló különféle technikákra , amelyek olyan hatást fejtenek ki, amelyet egy formális nyelven belül nehéz vagy lehetetlen lenne biztosítani .
A C és C++ nyelvek kifejezett gépelési szójátékokat biztosítanak olyan konstrukciókon keresztül, mint a casts , unionés reinterpret_casta C++ esetében is , bár ezeknek a nyelveknek a szabványai az ilyen szójátékok egyes eseteit meghatározatlan viselkedésként kezelik .
A Pascalban a variáns jelölések egy adott adattípus értelmezésére használhatók egynél több módon, vagy akár olyan módon is, amely nem a nyelvben honos.
A szójáték a szövegbiztonság közvetlen megsértését jelenti . Hagyományosan a gépelési szójáték létrehozásának képessége gyenge gépeléshezunsafe kapcsolódik, de egyes erősen gépelt nyelvek vagy megvalósításaik biztosítják ezt a képességet (általában a szavak vagy a hozzájuk tartozó azonosítók használatával unchecked). A típusbiztonság hívei azzal érvelnek, hogy a szójátékok „ szükségessége ” mítosz [1] .
A gépelési szójáték klasszikus példája látható a Berkeley socket felületén . Az a függvény , amely egy nyitott, inicializálatlan socketet köt egy IP-címhez , a következő aláírással rendelkezik:
int bind ( int sockfd , struct sockaddr * my_addr , socklen_t addrlen );A függvényt bindáltalában így hívják:
struct sockaddr_insa = { 0 } ; int sockfd = ...; sa . sin_family = AF_INET ; sa . sin_port = htons ( port ); bind ( sockfd , ( struct sockaddr * ) & sa , sizeof sa );A Berkeley Sockets Library alapvetően arra a tényre támaszkodik, hogy a C-ben egy mutató könnyen struct sockaddr_inkonvertálható mutatóvá , struct sockaddrés hogy a két struktúratípus átfedi egymást a memóriaszervezésben . Ezért egy mezőre mutató mutatómy_addr->sin_family (ahol típusamy_addr van ) valójában egy mezőre mutat (ahol típusa van ). Más szóval, a könyvtár gépelési szójátékot használ az öröklődés primitív formájának megvalósítására . [2] struct sockaddr*sa.sin_familysa struct sockaddr_in
A programozásban elterjedt a struktúrák - "rétegek" használata, amelyek lehetővé teszik a különféle típusú adatok hatékony tárolását egyetlen memóriablokkban . Leggyakrabban ezt a trükköt optimalizálási célból egymást kizáró adatokra használják .
Tegyük fel, hogy ellenőrizni szeretné, hogy egy lebegőpontos szám negatív-e. Ezt írhatná valaki:
bool is_negative ( float x ) { return x < 0,0 ; }A lebegőpontos összehasonlítások azonban erőforrás-igényesek, mivel a NaN esetében speciális módon működnek . Tekintettel arra, hogy a típus floataz IEEE 754-2008 szabvány szerint van ábrázolva , és a típus int32 bites , és ugyanazzal az előjelbittel rendelkezik, mint a -ban , gépelési szójáték segítségével kivonhatja egy lebegőpontos szám előjelbitjét, csak egész számot használva . összehasonlítás: float
bool is_negative ( float x ) { return * (( int * ) & x ) < 0 ; }Ez a gépelési szójáték a legveszélyesebb. Az előző példa csak a C nyelv által adott garanciákra támaszkodott a struktúra reprezentációja és a mutató konvertálhatósága tekintetében ; ez a példa azonban konkrét hardverfeltevéseken alapul . Egyes esetekben, például valós idejű alkalmazások fejlesztésekor , amelyeket a fordító nem képes önállóan optimalizálni , ilyen veszélyes programozási döntések szükségesek. Ilyen esetekben a megjegyzések és a fordítási idő ellenőrzései ( Static_assertions ) segítik a kód karbantarthatóságát .
Valódi példa található a Quake III kódban – lásd Fast Inverse Square Root .
A lebegőpontos számok bitenkénti ábrázolására vonatkozó feltételezéseken túl a gépelési szójáték fenti példája is sérti a C nyelv által az objektumok elérésére felállított szabályokat [3] : ként van deklarálva , de értéke egy típussal rendelkező kifejezés . Számos elterjedt platformon ez a mutatógépelési szójáték problémákhoz vezethet, ha a mutatók eltérően vannak elrendezve a memóriában . Ezenkívül a különböző méretű mutatók ugyanazon a memóriahelyen osztozhatnak , ami olyan hibákhoz vezethet , amelyeket a fordító nem észlel . xfloat signed int
Az aliasing problémája megoldható a használatával (bár az alábbi példa azon a feltételezésen alapul, hogy a lebegőpontos számot az IEEE-754 szabvány képviseli ): union
bool is_negative ( float x ) { szakszervezet { unsigned int ui ; úszó d ; } én_szakszervezetem = { . d = x }; return ( my_union . ui & 0x80000000 ) != 0 ; }Ez egy C99 kód , amely kijelölt inicializálókat használ . Amikor egy uniót hozunk létre , a valódi mező inicializálódik, majd a teljes mező értéke (fizikailag ugyanazon a címen található a memóriában) a szabvány s6.5 pontja szerint. Egyes fordítók nyelvi kiterjesztésként támogatják az ilyen konstrukciókat, például a GCC [4] .
A gépelési szójáték egy másik példáját lásd: Stride of an array .
A Változatok jelölése lehetővé teszi az adattípus különböző módokon történő figyelembevételét, a megadott változattól függően. A következő példa integer16 bitet longintés real32 bitet és character8 bitet feltételez:
type variant_record = rekord eset rec_type : longint of 1 : ( I : tömb [ 1..2 ] egész szám ) ; _ _ 2 : ( L : longint ) ; 3 : ( R : valódi ) ; 4 : ( C : tömb [ 1 .. 4 ] karakterből ) ; _ vége ; V változó : Változat_rekord ; K : Integer ; L.A .: Longint ; RA : Igazi ; Ch : karakter ; ... V . I := 1 ; Ch := V . C [ 1 ] ; (* Szerezd meg a VI mező első bájtját *) V . R = 8,3 ; LA := V . L ; (* A valós szám tárolása egész cellában *)Pascalban a valós egész számra másolása kerekített értékké alakítja át. Ez a módszer azonban egy bináris lebegőpontos értéket olyan hosszú egész számra (32 bit) alakít át, amely nem azonos, sőt egyes platformokon összeférhetetlen a hosszú egész számokkal.
Az ilyen példák furcsa transzformációkhoz használhatók, azonban bizonyos esetekben az ilyen konstrukcióknak van értelme, például bizonyos adatok helyének kiszámításakor. A következő példa feltételezi, hogy a mutató és a hosszú egész 32 bites:
Típus PA = ^ Arec ; Arec = rekord eset rt : longint of 1 : ( P : PA ) ; 2 : ( L : Longint ) ; vége ; Var PP : PA ; K : Longint ; ... Új ( PP ) ; P.P. ^. P := PP ; Writeln ( 'A PP változó a memóriában itt található: ' , hexa ( PP ^. L )) ;A Pascal szabványos eljárása Newarra szolgál, hogy dinamikusan lefoglalja a memóriát egy mutató számára, és hexegy olyan eljárás is magában foglalja, amely egy egész szám értékét leíró hexadecimális karakterláncot nyomtat. Ez lehetővé teszi a mutató címének megjelenítését, ami általában tilos (a Pascalban lévő mutatók nem olvashatók vagy nem adhatók ki – csak hozzárendelhetők). Érték hozzárendelése egy mutató egész változatához lehetővé teszi a rendszermemória bármely területének olvasását és módosítását:
P.P. ^. L : = 0 PP := PP ^. P ; (* PP a 0 címre mutat *) K := PP ^. L ; (*K a 0 címen lévő szó értékét tartalmazza *) Writeln ( 'A gép 0-s címén található szó tartalmazza a következőt:' , K ) ;Ez a program megfelelően működhet, vagy összeomolhat , ha a 0-s cím olvasásvédett, az operációs rendszertől függően.