มีบางประเด็นที่สำคัญที่ฉันคิดว่าคำตอบที่มีอยู่ทั้งหมดพลาดไป
การพิมพ์ที่อ่อนแอหมายถึงการอนุญาตให้เข้าถึงการเป็นตัวแทนพื้นฐาน ใน C ฉันสามารถสร้างตัวชี้ไปยังตัวละครแล้วบอกคอมไพเลอร์ฉันต้องการใช้มันเป็นตัวชี้ไปยังจำนวนเต็ม:
char sz[] = "abcdefg";
int *i = (int *)sz;
บนแพลตฟอร์มน้อย endian กับจำนวนเต็ม 32 บิตนี้จะทำให้i
เป็น array ของตัวเลขที่และ0x64636261
0x00676665
ในความเป็นจริงคุณสามารถทอดตัวชี้ไปที่จำนวนเต็ม (ขนาดที่เหมาะสม):
intptr_t i = (intptr_t)&sz;
และแน่นอนนี่หมายความว่าฉันสามารถเขียนทับหน่วยความจำได้ทุกที่ในระบบ *
char *spam = (char *)0x12345678
spam[0] = 0;
* แน่นอนว่าระบบปฏิบัติการสมัยใหม่ใช้หน่วยความจำเสมือนและการป้องกันหน้าดังนั้นฉันจึงสามารถเขียนทับหน่วยความจำของกระบวนการของฉันเองได้ แต่ไม่มีอะไรเกี่ยวกับตัว C ที่ให้การป้องกันเช่นเดียวกับใครก็ตามที่เคยเขียนโค้ด Classic Mac OS หรือ Win16 สามารถบอกคุณได้
Lisp ดั้งเดิมอนุญาตให้แฮ็กเกอร์ประเภทเดียวกัน บนแพลตฟอร์มบางตัวคำสองคำที่ลอยและเซลล์ข้อเสียเป็นประเภทเดียวกันและคุณสามารถส่งผ่านหนึ่งไปยังฟังก์ชันที่คาดหวังอีกอย่างหนึ่งและมันจะ "ทำงาน"
ภาษาส่วนใหญ่ทุกวันนี้ยังไม่อ่อนแอเท่า C และ Lisp แต่หลาย ๆ คนก็ยังรั่วอยู่ ตัวอย่างเช่นภาษา OO ใด ๆ ที่มี "downcast" ที่ไม่ถูกตรวจสอบ * เป็นประเภทที่มีการรั่วไหล: คุณกำลังบอกคอมไพเลอร์ว่า "ฉันรู้ว่าฉันไม่ได้ให้ข้อมูลเพียงพอที่จะรู้ว่าสิ่งนี้ปลอดภัย แต่ฉันค่อนข้างแน่ใจ มันคือ "เมื่อจุดรวมของระบบประเภทนั้นคอมไพเลอร์มีข้อมูลเพียงพอเสมอที่จะรู้ว่าอะไรปลอดภัย
* การดาวน์สตรีมที่ถูกตรวจสอบไม่ได้ทำให้ระบบประเภทของภาษาอ่อนแอลงเพียงเพราะมันย้ายการตรวจสอบไปยังรันไทม์ ถ้าเป็นเช่นนั้นแล้วความหลากหลายย่อย (เรียกว่าการเรียกใช้ฟังก์ชั่นเสมือนหรือแบบไดนามิกเต็มรูปแบบ) จะเป็นการละเมิดระบบประเภทเดียวกันและฉันไม่คิดว่ามีใครต้องการพูดแบบนั้น
ภาษา "สคริปต์" น้อยมากที่อ่อนแอในแง่นี้ แม้ใน Perl หรือ Tcl คุณไม่สามารถใช้สตริงและเพียงตีความไบต์เป็นจำนวนเต็ม * แต่มันก็คุ้มค่าที่จะสังเกตว่าใน CPython (และในทำนองเดียวกันสำหรับล่ามภาษาอื่น ๆ ในหลายภาษา) ถ้าคุณอดทนจริง ๆ คุณ สามารถใช้ctypes
ในการโหลดยกlibpython
วัตถุid
ไปยังPOINTER(Py_Object)
และบังคับให้ระบบประเภทการรั่วไหล ไม่ว่าสิ่งนี้จะทำให้ระบบประเภทอ่อนแอหรือไม่ขึ้นอยู่กับกรณีการใช้งานของคุณ - หากคุณกำลังพยายามใช้ Sandbox การดำเนินการที่ จำกัด ในภาษาเพื่อความปลอดภัยคุณต้องจัดการกับการหลบหนีเหล่านี้ ...
* คุณสามารถใช้ฟังก์ชั่นที่ต้องการstruct.unpack
อ่านไบต์และสร้าง int ใหม่จาก "วิธี C จะแสดงถึงไบต์เหล่านี้" แต่เห็นได้ชัดว่าไม่รั่วไหล; แม้ Haskell ก็ยอมให้
ในขณะเดียวกันการแปลงโดยนัยเป็นสิ่งที่แตกต่างจากระบบแบบอ่อนแอหรือรั่ว
ทุกภาษาแม้แต่ Haskell มีฟังก์ชั่นพูดแปลงค่าจำนวนเต็มเป็นสตริงหรือทศนิยม แต่ภาษาบางภาษาจะทำการแปลงให้คุณโดยอัตโนมัติเช่นใน C หากคุณเรียกใช้ฟังก์ชันที่ต้องการfloat
และคุณผ่านมันint
ไประบบจะแปลงให้คุณ สิ่งนี้สามารถนำไปสู่ข้อผิดพลาดเช่นล้นมากเกินคาด แต่พวกมันไม่ใช่บั๊กแบบเดียวกับที่คุณได้รับจากระบบที่อ่อนแอ และ C ไม่ได้อ่อนแอลงที่นี่จริงๆ คุณสามารถเพิ่ม int และการลอยใน Haskell หรือแม้กระทั่งการเชื่อมต่อการลอยกับสตริงคุณเพียงแค่ต้องทำอย่างชัดเจนมากขึ้น
และด้วยภาษาแบบไดนามิกนี่เป็นสิ่งที่ค่อนข้างมืดมน ไม่มีสิ่งเช่น "ฟังก์ชั่นที่ต้องการลอย" ใน Python หรือ Perl แต่มีฟังก์ชั่นโอเวอร์โหลดที่ทำสิ่งที่แตกต่างกับประเภทที่แตกต่างกันและมีความรู้สึกที่ใช้งานง่ายเช่นการเพิ่มสตริงในสิ่งอื่นคือ "ฟังก์ชั่นที่ต้องการสตริง" ในแง่นั้น Perl, Tcl และ JavaScript ดูเหมือนจะทำการแปลงโดยนัย ( "a" + 1
ให้คุณ"a1"
) จำนวนมากในขณะที่ Python ทำน้อยกว่ามาก ( "a" + 1
ยกข้อยกเว้น แต่1.0 + 1
ให้คุณ2.0
*) เป็นเรื่องยากที่จะนำความรู้สึกนั้นมาใช้ในรูปแบบทางการ - ทำไมไม่ควร+
ที่จะมีสตริงและ int เมื่อมีฟังก์ชั่นอื่น ๆ เช่นการจัดทำดัชนีอย่างชัดเจน
* ที่จริงแล้วใน Python สมัยใหม่นั้นสามารถอธิบายได้ในแง่ของการพิมพ์ย่อย OO เนื่องจากisinstance(2, numbers.Real)
เป็นจริง ฉันไม่คิดว่ามีความรู้สึกใดที่2
เป็นตัวอย่างของประเภทสตริงใน Perl หรือ JavaScript ... แม้ว่าใน Tcl มันเป็นจริงเพราะทุกอย่างเป็นตัวอย่างของสตริง
ในที่สุดก็มีคำจำกัดความของคำว่า "แข็งแรง" และ "อ่อนแอ" ที่สมบูรณ์โดยสิ้นเชิงซึ่งคำว่า "แข็งแรง" หมายถึงทรงพลัง / ยืดหยุ่น / แสดงออก
ตัวอย่างเช่น Haskell ช่วยให้คุณกำหนดประเภทที่เป็นตัวเลขสตริงรายการประเภทนี้หรือแผนที่จากสตริงเป็นประเภทนี้ซึ่งเป็นวิธีที่สมบูรณ์แบบในการแสดงสิ่งที่สามารถถอดรหัสจาก JSON ไม่มีวิธีกำหนดประเภทดังกล่าวใน Java แต่อย่างน้อย Java มีประเภทพารามิเตอร์ (ทั่วไป) ดังนั้นคุณสามารถเขียนฟังก์ชั่นที่ใช้ List of T และรู้ว่าองค์ประกอบนั้นเป็นประเภท T; ภาษาอื่น ๆ เช่น Java รุ่นก่อนบังคับให้คุณใช้ List of Object และ downcast แต่อย่างน้อย Java ช่วยให้คุณสร้างประเภทใหม่ด้วยวิธีการของตนเอง C ช่วยให้คุณสร้างโครงสร้างเท่านั้น และ BCPL ไม่ได้มีสิ่งนั้น และต่อไปจนถึงแอสเซมบลีที่ชนิดเท่านั้นมีความยาวบิตที่แตกต่างกัน
ดังนั้นในแง่นี้ระบบประเภทของ Haskell จึงแข็งแกร่งกว่า Java รุ่นใหม่ซึ่งแข็งแกร่งกว่า Java รุ่นก่อน ๆ ซึ่งแข็งแกร่งกว่า C ซึ่งแข็งแกร่งกว่า BCPL
Python จะพอดีกับสเปกตรัมนั้นอยู่ที่ไหน? นั่นเป็นเรื่องยุ่งยากเล็กน้อย ในหลายกรณีการพิมพ์เป็ดช่วยให้คุณจำลองทุกสิ่งที่คุณสามารถทำได้ใน Haskell และแม้แต่บางสิ่งที่คุณทำไม่ได้ ตรวจสอบว่ามีข้อผิดพลาดเกิดขึ้นขณะทำงานแทนที่จะรวบรวมเวลา แต่ก็ยังคงติดอยู่ อย่างไรก็ตามมีหลายกรณีที่การพิมพ์เป็ดไม่เพียงพอ ตัวอย่างเช่นใน Haskell คุณสามารถบอกได้ว่ารายการว่างของ ints เป็นรายการของ ints ดังนั้นคุณสามารถตัดสินใจได้ว่าการลดลงของ+
รายการนั้นควรส่งคืน 0 *; ใน Python รายการที่ว่างเปล่าเป็นรายการที่ว่างเปล่า ไม่มีข้อมูลประเภทที่จะช่วยให้คุณตัดสินใจได้ว่า+
ควรลดขนาดใดลง
* อันที่จริง Haskell ไม่ยอมให้คุณทำสิ่งนี้ ถ้าคุณเรียกใช้ฟังก์ชันลดที่ไม่ใช้ค่าเริ่มต้นในรายการว่างคุณได้รับข้อผิดพลาด แต่ระบบประเภทของมันมีพลังมากพอที่คุณจะทำให้งานนี้สำเร็จได้