อ่าน 8 นาที
พิมพ์คำเล่นสำนวน
ใน วิทยาการคอมพิวเตอร์ คำ ว่า "type punning" เป็นคำที่ใช้กันทั่วไปสำหรับเทคนิคการเขียนโปรแกรมใดๆ ที่บิดเบือนหรือหลีกเลี่ยง ระบบประเภทข้อมูล ของ ภาษาโปรแกรม...
พิมพ์คำเล่นสำนวน
ในวิทยาการคอมพิวเตอร์คำว่า "type punning"เป็นคำที่ใช้กันทั่วไปสำหรับเทคนิคการเขียนโปรแกรมใดๆ ที่บิดเบือนหรือหลีกเลี่ยงระบบประเภทข้อมูลของภาษาโปรแกรมเพื่อให้ได้ผลลัพธ์ที่ยากหรือเป็นไปไม่ได้ที่จะบรรลุได้ภายในขอบเขตของภาษา ที่เป็นทางการ
ใน ภาษา CและC++ มี โครงสร้างต่างๆ เช่นการแปลงประเภทพอยเตอร์ และ(C++ เพิ่มการแปลงประเภทอ้างอิงเข้าไปในรายการนี้ด้วย) เพื่อให้สามารถเล่นคำเกี่ยวกับประเภทได้หลายประเภท แม้ว่าบางประเภทจะไม่ได้รับการสนับสนุนโดยภาษามาตรฐานก็ตาม unionreinterpret_cast
ใน ภาษาโปรแกรม Pascalการใช้เรคอร์ดที่มีตัวแปรอาจใช้เพื่อจัดการกับชนิดข้อมูลเฉพาะในลักษณะมากกว่าหนึ่งวิธี หรือในลักษณะที่ไม่ได้รับอนุญาตตามปกติ
ตัวอย่างซ็อกเก็ต
ตัวอย่างคลาสสิกของการเล่นคำประเภท (type punning) พบได้ใน อินเทอร์เฟ ซซ็อกเก็ตของ Berkeleyฟังก์ชันสำหรับผูกซ็อกเก็ตที่เปิดอยู่แต่ยังไม่ได้เริ่มต้นใช้งานกับที่อยู่ IPนั้นประกาศไว้ดังนี้:
int bind ( int sockfd , struct sockaddr * my_addr , socklen_t addrlen );โดยปกติฟังก์ชัน นี้bindจะถูกเรียกใช้งานดังนี้:
#include <sys/socket.h>struct sockaddr_in sa = { . sin_family = AF_INET , . sin_port = htons ( port ) };int sockfd = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); bind ( sockfd , ( struct sockaddr * ) & sa , sizeof sa );ไลบรารีซ็อกเก็ตของ Berkeley อาศัยข้อเท็จจริงพื้นฐานที่ว่า ในภาษา Cนั้น ตัวชี้ไปstruct sockaddr_inยัง สามารถแปลงเป็นตัวชี้ไปยัง ได้อย่างอิสระstruct sockaddrและนอกจากนี้ โครงสร้างทั้งสองประเภทยังใช้รูปแบบการจัดเรียงหน่วยความจำแบบเดียวกัน ดังนั้น การอ้างอิงถึงฟิลด์ของโครงสร้างmy_addr->sin_family(โดยที่my_addrเป็นประเภทstruct sockaddr*) จะอ้างอิงถึงฟิลด์sa.sin_family(โดยที่saเป็นประเภทstruct sockaddr_in) จริงๆ แล้ว กล่าวอีกนัยหนึ่ง ไลบรารีซ็อกเก็ตใช้การเล่นคำประเภท (type punning) เพื่อสร้างรูปแบบพื้นฐานของพหุรูป (polymorphism ) หรือการสืบทอด (inheritance )
ในโลกของการเขียนโปรแกรม เรามักพบเห็นการใช้โครงสร้างข้อมูลที่ "มีส่วนเพิ่มเติม" เพื่อให้สามารถจัดเก็บค่าประเภทต่างๆ ในพื้นที่จัดเก็บเดียวกันได้ โดยมักพบเห็นเมื่อมีการใช้โครงสร้างสองแบบแยกจากกันโดยสิ้นเชิงเพื่อเพิ่มประสิทธิภาพ
ตัวอย่างจุดลอยตัว
ไม่ใช่ทุกตัวอย่างของการเล่นคำเกี่ยวกับชนิดข้อมูลจะเกี่ยวข้องกับโครงสร้างข้อมูลเสมอไป ดังเช่นตัวอย่างก่อนหน้านี้ สมมติว่าเราต้องการตรวจสอบว่า จำนวน ทศนิยมเป็นจำนวนลบหรือไม่ เราสามารถเขียนได้ดังนี้:
bool is_negative ( float x ) { return x < 0.0f ; }อย่างไรก็ตาม หากสมมติว่าการเปรียบเทียบเลขทศนิยมมีค่าใช้จ่ายสูง และสมมติว่าเลข ทศนิยม floatถูกแสดงตามมาตรฐานเลขทศนิยมของ IEEEและจำนวนเต็มมีความกว้าง 32 บิต เราสามารถใช้เทคนิค type punning เพื่อแยกบิตเครื่องหมายของเลขทศนิยมโดยใช้เพียงการดำเนินการกับจำนวนเต็มได้:
bool is_negative ( float x ) { int * i = ( int * ) & x ; return * i < 0 ; }โปรดทราบว่าพฤติกรรมจะไม่เหมือนกันทุกประการ: ในกรณีพิเศษที่xเป็นศูนย์ลบการใช้งานแบบแรกจะให้ผลลัพธ์falseในขณะที่การใช้งานแบบที่สองจะให้ผลลัพธ์trueนอกจากนี้ การใช้งานแบบแรกจะส่งคืนค่า สำหรับค่า NaNfalseใดๆแต่การใช้งานแบบหลังอาจส่งคืนค่าสำหรับค่า NaN ที่มีบิตเครื่องหมายถูกตั้งค่าไว้ สุดท้าย เรายังมีปัญหาที่ว่าการจัดเก็บข้อมูลจุดลอยตัวอาจอยู่ในลำดับหน่วยความจำแบบ big endian หรือ little endian และดังนั้นบิตเครื่องหมายอาจอยู่ในไบต์ที่มีค่าน้อยที่สุดหรือไบต์ที่มีค่ามากที่สุด ดังนั้นการใช้ type punning กับข้อมูลจุดลอยตัวจึงเป็นวิธีการที่น่าสงสัยและให้ผลลัพธ์ที่คาดเดาไม่ได้ true
การเล่นคำเกี่ยวกับชนิดข้อมูลแบบนี้อันตรายกว่าแบบอื่นๆ ในขณะที่ตัวอย่างซ็อกเก็ตอาศัยเพียงแค่การรับประกันจากภาษาการเขียนโปรแกรม C เกี่ยวกับโครงสร้างและการแปลงพอยเตอร์floatตัวอย่างนี้อาศัยข้อสมมติเกี่ยวกับฮาร์ดแวร์ของระบบเฉพาะ ข้อกำหนดภาษา C99 (ISO9899:1999) มีคำเตือนต่อไปนี้ในส่วนที่ 6.3.2.3 พอยเตอร์: "พอยเตอร์ไปยังวัตถุหรือชนิดข้อมูลที่ไม่สมบูรณ์อาจถูกแปลงเป็นพอยเตอร์ไปยังวัตถุหรือชนิดข้อมูลที่ไม่สมบูรณ์อื่น หากพอยเตอร์ที่ได้ไม่ได้รับการจัดเรียงอย่างถูกต้องสำหรับชนิดข้อมูลที่ชี้ไป พฤติกรรมจะไม่มีการกำหนด" ดังนั้นจึงควรระมัดระวังเป็นอย่างมากในการใช้การเล่นคำเกี่ยวกับชนิดข้อมูล
ในบางสถานการณ์ เช่นโค้ดที่สำคัญต่อเวลาในการประมวลผล ซึ่ง คอมไพเลอร์ไม่สามารถปรับแต่งให้เหมาะสมได้อาจจำเป็นต้องใช้โค้ดที่มีความเสี่ยง ในกรณีเหล่านี้ การบันทึกข้อสมมติฐานทั้งหมดไว้ในความคิดเห็นและการใช้คำสั่งยืนยันแบบคงที่เพื่อตรวจสอบความคาดหวังด้านความเข้ากันได้กับแพลตฟอร์มต่างๆ จะช่วยให้โค้ดสามารถบำรุงรักษาได้ง่ายขึ้น
ตัวอย่างการใช้งานการเล่นคำแบบจุดลอยตัว ได้แก่ การหาค่าผกผันของรากที่สองอย่างรวดเร็วซึ่งเป็นที่นิยมในเกม Quake IIIการเปรียบเทียบ FP อย่างรวดเร็วเป็นจำนวนเต็ม[ 1 ]และการค้นหาค่าใกล้เคียงโดยการเพิ่มค่าเป็นจำนวนเต็ม (การใช้งานnextafter) [ 2 ]
โดยภาษา
ซี และ ซี++
นอกเหนือจากสมมติฐานเกี่ยวกับการแสดงบิตของตัวเลขจุดลอยตัวแล้ว ตัวอย่างการเล่นประเภทจุดลอยตัวข้างต้นยังละเมิดข้อจำกัดของภาษา C เกี่ยวกับวิธีการเข้าถึงวัตถุอีกด้วย: [ 3 ]ประเภทที่ประกาศของxคือfloatแต่ถูกอ่านผ่านนิพจน์ของประเภทunsigned intบนแพลตฟอร์มทั่วไปหลายๆ แพลตฟอร์ม การใช้การเล่นตัวชี้แบบนี้อาจสร้างปัญหาได้หากตัวชี้ที่แตกต่างกันถูกจัดเรียงในลักษณะเฉพาะของเครื่องยิ่งไปกว่านั้น ตัวชี้ที่มีขนาดต่างกันอาจเข้าถึงหน่วยความจำเดียวกันได้ทำให้เกิดปัญหาที่คอมไพเลอร์ไม่ได้ตรวจสอบ แม้ว่าขนาดข้อมูลและการแสดงตัวชี้จะตรงกันก็ตาม คอมไพเลอร์ก็ยังสามารถอาศัยข้อจำกัดที่ไม่ทำให้เกิดการเข้าถึงซ้ำซ้อนเพื่อทำการเพิ่มประสิทธิภาพซึ่งจะไม่ปลอดภัยหากมีการเข้าถึงซ้ำซ้อนที่ไม่ได้รับอนุญาต
การใช้ตัวชี้
การพยายามเล่นคำโดยใช้ประเภทข้อมูลแบบง่ายๆ สามารถทำได้โดยใช้พอยเตอร์: (ตัวอย่างต่อไปนี้สมมติว่าใช้การแสดงบิตแบบ IEEE-754 สำหรับประเภทข้อมูลfloat.)
// ในภาษา C bool is_negative ( float x ) { int32_t i = * ( int32_t * ) & x ; return i < 0 ; }// ใน C++ bool is_negative ( float x ) { int32_t i = * reinterpret_cast < int32_t *> ( & x ); return i < 0 ; }กฎการกำหนดนามแฝงของมาตรฐาน C ระบุว่าวัตถุจะต้องสามารถเข้าถึงค่าที่จัดเก็บไว้ได้โดยใช้เพียงนิพจน์ lvalue ของประเภทที่เข้ากันได้เท่านั้น[ 4 ]ประเภทfloatและint32_tไม่เข้ากัน ดังนั้นพฤติกรรมของโค้ดนี้จึงไม่แน่นอนแม้ว่าใน GCC และLLVMโปรแกรมนี้จะคอมไพล์และทำงานได้ตามที่คาดไว้ แต่ตัวอย่างที่ซับซ้อนกว่าอาจโต้ตอบกับสมมติฐานที่กำหนดโดยการกำหนดนามแฝงอย่างเข้มงวดและนำไปสู่พฤติกรรมที่ไม่พึงประสงค์ ตัวเลือกนี้-fno-strict-aliasingจะช่วยให้มั่นใจได้ว่าโค้ดที่ใช้รูปแบบการกำหนดประเภทแบบนี้มีพฤติกรรมที่ถูกต้อง แม้ว่าจะแนะนำให้ใช้รูปแบบการกำหนดประเภทอื่นๆ ก็ตาม[ 5 ]
การใช้union
ในภาษา C แต่ไม่ใช่ใน C++ บางครั้งสามารถทำการเปลี่ยนประเภทข้อมูลโดยใช้union. ได้
bool is_negative ( float x ) { union { int i ; float f ; } my_union ; my_union . f = x ; return my_union . i < 0 ; }การเข้าถึงmy_union.iหลังจากเขียนไปยังสมาชิกอื่นล่าสุดmy_union.fถือเป็นรูปแบบหนึ่งของการเล่นคำประเภทที่อนุญาตใน C [ 6 ]โดยมีเงื่อนไขว่าสมาชิกที่อ่านจะต้องไม่ใหญ่กว่าสมาชิกที่มีการตั้งค่าค่า (มิฉะนั้นการอ่านจะมีพฤติกรรมที่ไม่ระบุ[ 7 ] ) เช่นเดียวกันนี้ถูกต้องตามหลักไวยากรณ์ แต่มีพฤติกรรมที่ไม่กำหนดใน C++ [ 8 ]โดยที่สมาชิกที่เขียนล่าสุดเท่านั้นunionที่จะถือว่ามีค่า
สำหรับตัวอย่างอื่นของการเล่นคำเกี่ยวกับชนิดข้อมูล โปรดดูที่ Stride ของอาร์เรย์
การใช้memcpy
memcpyเป็นวิธีการเล่นประเภทที่ปลอดภัยและพกพาได้[ 9 ]ซึ่งได้รับการรับรองในมาตรฐาน C++ [ 10 ] Clangและ GCC มีการเพิ่มประสิทธิภาพเฉพาะสำหรับmemcpyการเรียกใช้ประเภทนี้[ 11 ]
#include <string.h>bool is_negative ( float x ) { int i ; memcpy ( & i , & x , sizeof ( int )); // หรือ std::memcpy ใน C++ return i < 0 ; }การใช้bit_cast
ในC++20 ฟังก์ชัน นี้std::bit_castอนุญาตให้ใช้ประเภท punning โดยไม่มีพฤติกรรมที่ไม่กำหนด นอกจากนี้ยังอนุญาตให้ติดป้ายกำกับฟังก์ชันได้constexpr[ 10 ] การใช้งานอ้างอิงเป็นตัวห่อหุ้มstd::memcpyรอบ[ 12 ]
import std ;โดยใช้std :: numeric_limits ;constexpr bool is_negative ( float x ) noexcept { static_assert ( numeric_limits < float >:: is_iec559 ); // (เปิดใช้งานเฉพาะบน IEEE 754) int32_t i = std :: bit_cast < int32_t > ( x ); return i < 0 ; }กลไกการเล่นคำประเภทอื่นถูกรวมไว้ใน C++ ในC++ 23ด้วยและ[ 13 ]std::start_lifetime_asstd::start_lifetime_as_array
ปาสคาล
ระเบียนตัวแปรอนุญาตให้จัดการชนิดข้อมูลหนึ่งๆ เป็นข้อมูลหลายประเภท ขึ้นอยู่กับตัวแปรที่อ้างอิงถึง ในตัวอย่างต่อไปนี้ ตัวแปรจำนวนเต็ม (integer)ถือว่ามีขนาด 16 บิต ในขณะที่ตัวแปรจำนวนเต็มยาว (longint ) และตัวแปรจำนวนจริง (real)ถือว่ามีขนาด 32 บิต และตัวแปรอักขระ (character) ถือว่ามีขนาด 8 บิต:
type VariantRecord = record case RecType : LongInt of 1 : ( I : array [ 1 .. 2 ] of Integer ) ; (* ไม่แสดงที่นี่: อาจมีตัวแปรหลายตัวในคำสั่ง case ของ variant record *) 2 : ( L : LongInt ) ; 3 : ( R : Real ) ; 4 : ( C : array [ 1 .. 4 ] of Char ) ; end ;var V : VariantRecord ; K : Integer ; LA : LongInt ; RA : Real ; Ch : Character ;V . I [ 1 ] := 1 ; Ch := V . C [ 1 ] ; (* นี่จะเป็นการดึงไบต์แรกของ VI ออกมา *) V . R := 8.3 ; LA := V . L ; (* นี่จะเป็นการจัดเก็บค่า Real ลงในค่า Integer *)ในภาษา Pascal การคัดลอกค่าทศนิยมไปยังจำนวนเต็มจะแปลงเป็นค่าที่ถูกตัดทอน วิธีการนี้จะแปลงค่าไบนารีของจำนวนทศนิยมไปเป็นค่าจำนวนเต็มแบบ long (32 บิต) ซึ่งจะไม่เหมือนกันและอาจไม่เข้ากันกับค่าจำนวนเต็มแบบ long ในบางระบบ
ตัวอย่างเหล่านี้สามารถนำไปใช้สร้างการแปลงที่แปลกประหลาดได้ แม้ว่าในบางกรณี โครงสร้างประเภทนี้อาจมีประโยชน์อย่างถูกต้องตามกฎหมาย เช่น การกำหนดตำแหน่งของข้อมูลเฉพาะบางส่วน ในตัวอย่างต่อไปนี้ ตัวชี้และ longint ต่างก็ถูกสันนิษฐานว่าเป็น 32 บิต:
ประเภทPA = ^ Arec ;Arec = บันทึกกรณีRT : LongInt ของ1 : ( P : PA ) ; 2 : ( L : LongInt ) ; สิ้นสุด;var PP : PA ; K : LongInt ;ใหม่( PP ) ; PP ^. P := PP ; เขียนข้อความ( 'ตัวแปร PP อยู่ที่ที่อยู่ ' , เลขฐานสิบหก( PP ^. L )) ;โดยที่ "new" คือรูทีนมาตรฐานในภาษาปาสคาลสำหรับการจัดสรรหน่วยความจำให้กับพอยเตอร์ และ "hex" น่าจะเป็นรูทีนสำหรับพิมพ์สตริงเลขฐานสิบหกที่อธิบายค่าของจำนวนเต็ม ซึ่งจะทำให้สามารถแสดงที่อยู่ของพอยเตอร์ได้ ซึ่งโดยปกติแล้วทำไม่ได้ (พอยเตอร์ไม่สามารถอ่านหรือเขียนได้ แต่สามารถกำหนดค่าได้เท่านั้น) การกำหนดค่าให้กับพอยเตอร์ชนิดจำนวนเต็มจะทำให้สามารถตรวจสอบหรือเขียนไปยังตำแหน่งใดก็ได้ในหน่วยความจำของระบบ:
PP ^. L := 0 ; PP := PP ^. P ; (* PP ตอนนี้ชี้ไปยังแอดเดรส 0 *) K := PP ^. L ; (* K มีค่าของเวิร์ด 0 *) WriteLn ( 'เวิร์ด 0 ของเครื่องนี้มีค่า ' , K ) ;โครงสร้างนี้อาจทำให้เกิดการตรวจสอบโปรแกรมหรือการละเมิดการป้องกัน หากแอดเดรส 0 ถูกป้องกันการอ่านบนเครื่องที่โปรแกรมกำลังทำงานอยู่ หรือ บน ระบบปฏิบัติการที่โปรแกรมกำลังทำงานอยู่
เทคนิคการแปลงค่าแบบ reinterpret cast จากภาษา C/C++ ก็สามารถใช้ได้ในภาษา Pascal เช่นกัน ซึ่งอาจมีประโยชน์ เช่น เมื่ออ่านค่า dword จากสตรีมไบต์ และต้องการประมวลผลค่าเหล่านั้นเหมือน float ต่อไปนี้เป็นตัวอย่างการทำงานที่แสดงการแปลงค่า dword เป็น float แบบ reinterpret cast:
ประเภทpReal = ^ Real ;var DW : DWord ; F : Real ;F := pReal ( @ DW ) ^;ซี#
ใน ภาษา C# (และภาษา .NET อื่นๆ) การเปลี่ยนประเภทข้อมูลทำได้ยากกว่าเล็กน้อยเนื่องจากระบบประเภทข้อมูล แต่ก็สามารถทำได้โดยใช้พอยเตอร์หรือโครงสร้างยูเนียน
จุดชี้
ภาษา C# อนุญาตให้ใช้พอยเตอร์เฉพาะกับประเภทข้อมูลพื้นฐาน (native types) เท่านั้น กล่าวคือ ประเภทข้อมูลดั้งเดิม (ยกเว้น `int` string), enum, array หรือ struct ที่ประกอบขึ้นจากประเภทข้อมูลพื้นฐานอื่นๆ เท่านั้น โปรดทราบว่า อนุญาตให้ใช้พอยเตอร์ได้เฉพาะในบล็อกโค้ดที่ทำเครื่องหมายว่า 'unsafe' เท่านั้น
unsafe { float pi = 3.14159 ; uint piAsRawData = * ( uint * ) & pi ; }สหภาพแรงงานโครงสร้าง
การเชื่อมต่อโครงสร้างสามารถทำได้โดยไม่มีแนวคิดเรื่อง "ความไม่ปลอดภัย" แต่จำเป็นต้องมีการกำหนดประเภทใหม่ขึ้นมา
[StructLayout(LayoutKind.Explicit)] struct FloatAndUIntUnion { [FieldOffset(0)] public float DataAsFloat ;[FieldOffset(0)] public uint DataAsUInt ; }// ...FloatAndUIntUnion union ; union.DataAsFloat = 3.14159 ; uint piAsRawData = union.DataAsUInt ;รหัส CIL ดิบ
สามารถใช้ CILแบบดิบแทน C# ได้ เนื่องจากไม่มีข้อจำกัดด้านประเภทข้อมูลส่วนใหญ่ ซึ่งทำให้สามารถรวมค่า enum สองค่าของประเภททั่วไปเข้าด้วยกันได้ ตัวอย่างเช่น:
TEnum a = ...; TEnum b = ...; TEnum combined = a | b ; // ไม่ถูกต้องสามารถหลีกเลี่ยงปัญหานี้ได้โดยใช้รหัส CIL ต่อไปนี้:
. เมธอดสาธารณะแบบคงที่hidebysig !! TEnum CombineEnums < valuetype . ctor ([ mscorlib ] System . ValueType ) TEnum > ( !! TEnum a , !! TEnum b ) cil managed { . maxstack 2ldarg .0 ldarg .1 หรือ// สิ่งนี้จะไม่ทำให้เกิดการล้น เนื่องจาก a และ b มีชนิดเดียวกัน ดังนั้นจึงมีขนาดเท่ากันret }โอเปอเรเตอร์ CIL อนุญาตให้ใช้เทคนิคอื่นๆ cpblkได้อีก เช่น การแปลงโครงสร้างข้อมูลเป็นอาร์เรย์ไบต์:
. เมธอดสาธารณะแบบคงที่hidebysig uint8 [] ToByteArray < valuetype . ctor ([ mscorlib ] System . ValueType ) T > ( !! T & v // 'ref T' ใน C# ) cil managed { . locals init ( [0] uint8 [] ). แม็กซ์สแต็ค3// สร้างอาร์เรย์ไบต์ใหม่ที่มีความยาว sizeof(T) และเก็บไว้ในตัวแปรโลคอล 0 sizeof !! T newarr uint8 dup // เก็บสำเนาไว้บนสแต็กเพื่อใช้ในภายหลัง (1) stloc .0ldc . i4 .0 ldelema uint8// memcpy(local 0, &v, sizeof(T)); // <อาร์เรย์ยังคงอยู่บนสแต็ก ดู (1)> ldarg .0 // นี่คือ *ที่อยู่* ของ 'v' เพราะประเภทของมันคือ '!!T&' sizeof !! T cpblkldloc .0 ret }ชวา
ต่างจาก C/C++ ที่อนุญาตให้เข้าถึงหน่วยความจำและคำนวณเลขชี้กำลังได้ Java ไม่รองรับฟังก์ชันเหล่านี้ (อย่างเป็นทางการ) อย่างไรก็ตาม สามารถจำลองการเปลี่ยนประเภทข้อมูลได้ในลักษณะเดียวกัน
โดยใช้java.nio.ByteBuffer
import java.nio.ByteBuffer ;void main ( String [] args ) { int value = 42 ;ByteBuffer buffer = ByteBuffer.allocate ( Integer.BYTES ) ; buffer.putInt ( value ) ;// แปลงข้อมูลนี้เป็น float (เป็นเพียงตัวอย่าง) buffer.flip ( ) ; float punResult = buffer.getFloat ( ) ; }โดยใช้sun.misc.Unsafe
การใช้sun.misc.Unsafeทำให้สามารถทำ type punning ได้โดยตรง โดยใช้การ เข้าถึงหน่วยความจำโดยตรง
import java.lang.reflect.Field ; import sun.misc.Unsafe ;void main ( String [ ] args ) throws NoSuchFieldException , IllegalAccessException { Field f = Unsafe.class.getDeclaredField ( " theUnsafe " ) ; f.setAccessible ( true ) ; Unsafe unsafe = ( Unsafe ) f.get ( null ) ;ที่อยู่ยาว= unsafe.allocateMemory ( 4 ) ; unsafe.putInt ( address , 42 ) ;// ตีความตำแหน่งหน่วยความจำเป็นค่า float float result = unsafe.getFloat ( address ) ; unsafe.freeMemory ( address ) ; }ลิงก์ภายนอก
- ส่วนหนึ่งของคู่มือGCC
-fstrict-aliasingเกี่ยวกับเรื่องนี้ ซึ่งเอาชนะการเล่นคำบางประเภทได้ - รายงานข้อบกพร่องหมายเลข 257ของ มาตรฐาน C99ซึ่งบังเอิญให้คำจำกัดความของ "type punning" ในแง่ของ
unionและอภิปรายประเด็นต่างๆ ที่เกี่ยวข้องกับพฤติกรรมที่กำหนดโดยการใช้งานของตัวอย่างสุดท้ายข้างต้น - รายงานข้อบกพร่องหมายเลข 283เกี่ยวกับการใช้ยูเนียนสำหรับการเล่นคำประเภท