Max & Min
Primitive type ใน C# เช่น short float หรือ int จะมี min และ max value เป็นค่าต่ำสุดและสูงสุดที่เป็นไปได้ ค่านี้ถูกกำหนดโดยจำนวน bit ของ type เช่น int มี 32 bit เลขฐานสองขนาด 32 bit สามารถแสดงตัวเลขได้ถึง 4294967296 แต่เนื่องจาก int เป็น sign integer ต้องใช้ 1 bit สำหรับเก็บ flag + หรือ - ดังนั้น bit ที่ใช้สำหรับเก็บตัวเลขจึงเหลือ 31 bit ทำให้สามารถเก็บค่าต่ำสุดและสูงสุดดังนี้
1 | public const Int32 MaxValue = 2147483647; |
จากปัจจัยนี้ทำให้ int ไม่สามารถใช้เก็บค่าที่ต่ำหรือสูงกว่า min และ max นี้ได้ ถ้าพยายาม assign ค่านอกจาก range นี้ก็จะเกิดสิ่งที่เรียกกว่า overflow
ภาษาต่าง ๆ จะมีวิธีจัดการกับ overflow ที่ต่างกัน เช่น C, C++ จะไม่สนใจ overflow และไม่ถือว่า overflow คือ error ดังนั้นโปรแกรมเมอร์ ต้องควบคุมจัดการเอง
ภาษา Visual Basic จะตรวจจับ overflow ขณะทำงาน และจะ throw exception ออกมาเมื่อเจอเคสแบบนี้
ส่วน C# โดย default จะปิดการตรวจจับ overflow ไว้ คือ unchecked นั่นเอง แต่สามารถเปิดให้ checked ได้ด้วยการ compile ด้วย flag /checked+
ซึ่ง compiler จะเพิ่มโค้ดชุดพิเศษแทนที่ชุดปกติ เช่น เปลี่ยน IL instruction จาก add
เป็น add.ovf
ทดสอบ
มาดูกันว่าโปรแกรม Test.cs
ที่จะใช้ทดสอบเมื่อ compile ด้วย flag /checked+
โปรแกรมที่ได้นั้นต่างกับโปรแกรมปกติอย่างไร
1 | using System; |
ลอง compile Test.cs เป็นสองแบบ คือ แบบธรรมดา และแบบมี /checked+
- Compile Test.cs เป็น exe ก่อนด้วย dmcs
- Decompile exe ที่ได้จากข้อ 1 ด้วย monodis เพื่อเช็ค IL instruction
Compile และ decompile แบบธรรมดา
1 | dmcs -out:Unchecked.exe Unchecked.cs |
Compile ด้วย /checked+
1 | dmcs /checked+ -out:Checked.exe Checked.cs |
IL instruction
IL instruction ของโปรแกรมต่างกันอย่างไร
แบบที่ 1 Unchecked.il ได้จาก Unchecked.exe
1 | // method line 2 |
แบบที่ 2 Checked.il ได้จาก Checked.exe
1 | // method line 2 |
จะเห็นว่า IL instruction ของทั้งสองโปรแกรมมีหน้าแทบเหมือนกัน ยกเว้นเพียงบรรทัดที่ 14 ที่มี instruction ต่างกัน คือ add
กับ add.ovf
การทำงาน
จาก IL ที่ได้จะเห็นว่า มีเพียง instruction เดียวที่ต่างกัน แล้วโปรแกรมทั้งสองจะทำงานต่างกันหรือไม่
1. รันโปรแกรมแบบ unchecked
รันโปรแกรมได้ปกติ ไม่เกิด exception แต่ได้ผลลัพท์ คือ -2147483648
ซึ่งผิดคาดจากที่ตั้งใจไว้ ตาม sense การบวกเลขเราน่าจะได้ผลลัพท์ลักษณะนี้ 2147483647 + 1 = 21474836478
1 | $ mono Unchecked.exe |
2. โปรแกรมแบบ checked
จะมี error คือ System.OverflowException ซึ่งหมายความว่า CLR ตรวจเจอ overflow จึงทำการ throw exception ออกมา
1 | $ mono Checked.exe |
สรุป
จากการทดสอบจะเห็นว่า โปรแกรมที่ไม่เช็ค overflow สามารถทำงานได้ โดยไม่มี error แต่ผลลัพท์ไม่ถูกต้อง ในการใช้งานจริงโปรแกรมเมอร์ต้องแน่ใจว่า โค้ดที่เขียนจะต้องอยู่ภายใน range ของ type นั้น มิฉะนั้นโปรแกรมก็จะทำงานผิดพลาด
ส่วนโปรแกรมที่เช็ค overflow ซึ่งโดยปกติจะมีประสิทธิภาพต่ำกว่าแบบ unchecked เล็กน้อย จะมี runtime exception และไม่แสดงผลลัพท์ใด ๆ ในทางปฏิบัติโปรแกรมเมอร์สามารถจัดการ exception ที่เกิดขึ้นโดยใช้ประโยค try ... catch
จากนั้นก็เขียนโลจิกเพิ่มเติมเพื่อกัดการกับ error เพื่อช่วยป้องกันความผิดพลาดของโปรแกรม
สรุปสั้น ๆ คือ
- unchecked เร็วกว่า
- checked ปลอดภัยกว่า
หมายเหตุ
นอกจากการใช้ compiler flag /checked+
ซึ่งจะมีผลกับ instruction ของทั้งโปรแกรม เราสามารถเขียนโค้ดสำหรับ checked หรือ unchecked overflow เฉพาะส่วนที่ต้องการได้ โดยใช้ operator checked
กับ unchecked
ตามตัวอย่างนี้
1 | using System; |