Szójáték

Az oldal jelenlegi verzióját még nem ellenőrizték tapasztalt hozzászólók, és jelentősen eltérhet a 2017. október 19-én felülvizsgált verziótól ; az ellenőrzések 11 szerkesztést igényelnek .

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] .

Példa: sockets

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 .

Példa: lebegőpontos számok

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 unió használata

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   .

Pascal

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.

Lásd még

Jegyzetek

  1. Lawrence C. Paulson. ML a Working Programmer számára. — 2. - Cambridge, Nagy-Britannia: Cambridge University Press, 1996. - S. 2. - 492 p. - ISBN 0-521-57050-6 (kemény kötés), 0-521-56543-X (puha kötés).
  2. struct sockaddr_in, struct in_addr . www.gta.ufrj.br. Hozzáférés időpontja: 2016. január 17. Az eredetiből archiválva : 2016. január 24.
  3. ISO/IEC 9899:1999 s6.5/7
  4. GCC: Nem hibák . Letöltve: 2014. november 21. Az eredetiből archiválva : 2014. november 22..

Linkek