พื้นฐานการเขียนโปรแกรม:การเขียนโปรแกรมแบบปลอดบัก
การทำอะไรสักอย่าง ถ้าไม่รอบคอบแน่นอนว่าจะมีข้อผิดพลาดเกิดขึ้นได้ง่าย ต่อให้รอบคอบแค่ไหน บางครั้งข้อผิดพลาดก็ยังเกิดขึ้นได้ เช่นเดียวกับการเขียนโปรแกรม ต่อให้โปรแกรมง่ายแค่ไหน สั้นแค่ไหน ก็มีโอกาสมีข้อผิดพลาดในโปรแกรมนั้นได้เสมอ จนมีคำกล่าวที่ว่า "มีข้อผิดพลาดอย่างน้อยหนึ่งที่ที่ยังหาไม่พบในโปรแกรมเสมอ"
เนื้อหา
- 1 อะไรคือบัก
- 2 บักเกิดขึ้นได้อย่างไรบ้าง
- 2.1 Divide by zero:หารด้วยศูนย์
- 2.2 Infinite loops:ลูปอนันต์
- 2.3 Arithmetic overflow or underflow:ค่าเกินขอบเขตตัวแปร
- 2.4 Exceeding array bounds:ค่าตัวชี้เกินขนาดของตัวแปรArray
- 2.5 Using an uninitialized variable:ใช้ตัวแปรที่ไม่ได้มีการกำหนดค่า
- 2.6 Accessing memory not owned (Access violation):การอ้างค่าจากหน่วยความจำที่ไม่มีสิทธิ์เข้าถึง
- 2.7 Memory leak or Handle leak:หน่วยความจำไม่พอ
- 2.8 Stack overflow or underflow:ปัญหาสแตกล้น และปัญหาข้อมูลในสแตกไม่พอ
- 2.9 Buffer overflow:
- 2.10 Deadlock:การแย่งทรัพยากร
- 2.11 Off by one error:
- 2.12 Race hazard:
- 2.13 Loss of precision in type conversion:
- 3 หลักการลดจำนวนบัก
อะไรคือบัก
เกริ่นเรื่องข้อผิดพลาดในโปรแกรมมาตั้งนาน ท่านผู้อ่านคงเริ่มสงสัยแล้วว่า ชื่อหัวข้อที่ว่า "การเขียนโปรแกรมแบบปลอดบัก" เกี่ยวข้องยังไงกับข้อผิดพลาด หลายๆท่านคงเริ่มเดาออกแล้วว่า เจ้า บัก นี่แหละ หมายถึงข้อผิดพลาดในโปรแกรม แต่บางท่านคงยังไม่ทราบว่าเหตุใดจึงเรียก ข้อผิดพลาดในโปรแกรมว่า บัก
บัก ในภาษาอังกฤษ เขียนว่า bug แปลว่า แมลง โดยในสมัยก่อน ช่วงที่คอมพิวเตอร์ยังเป็นเครื่องใหญ่ขนาดเท่าห้องอยู่นั้นอุปกรณ์แต่ละส่วนของคอมพิวเตอร์ก็ใหญ่กว่าในปัจจุบันมาก ใหญ่พอที่จะให้แมลงลงไปอยู่ระหว่างอุปกรณ์ต่างๆภายในเครื่องได้ โดยแมลงตัวแรกที่ถูกพบว่าไปติดอยู่ในเครื่องคอมพิวเตอร์ซึ่งเป็นเหตุให้เกิดข้อผิดพลาดขึ้นนั้น เป็นผีเสื้อกลางคืน ซึ่งถูกพบโดย Grace Murray Hopper ในเครื่อง Mark II ณ มหาวิทยาลัยฮาร์วาร์ด Grace Murray Hopperได้นำเอาซากของผีเสื้อกลางคืนออกจากเครื่อง Mark II และนำไปแปะไว้ในสมุดบันทึก(log book) ซึ่งการกระทำนี้เอง เป็นต้นกำเนิดของคำว่า ดีบัก(debug)
อ่านเพิ่มเติมได้ที่ประวัติของ"บัก"
บักเกิดขึ้นได้อย่างไรบ้าง
สาเหตุหลักๆที่ทำให้เกินบักได้แก่
Divide by zero:หารด้วยศูนย์
ก่อนอื่นลองดูตัวอย่างโปรแกรมข้างล่างกันก่อน
... double a,b,c; ... a=b/c; ...
จะเห็นได้ว่ามีการนำค่าในตัวแปร b หารด้วยค่าในตัวแปร c แล้วนำค่าที่ได้มาใส่ในตัวแปรชื่อ a ซึ่งการกระทำนี้จะไม่มีผลอะไรถ้าตัวแปร c ไม่เป็น 0 เนื่องจากเรารู้กันอยู่แล้วว่านั้นไม่มีความหมายทางคณิตศาสตร์ แต่ค่าอื่นที่ไม่ใช่ 0 ก็ไม่สามารถนำ 0 ไปหารได้้้ ดังนั้นการหารด้วย0จึงเป็นการกระทำที่ไม่สามารถหาคำตอบที่ถูกต้องได้ ซึ่งถ้าสั่งให้คอมพิวเตอร์คำนวน ก็จะได้ค่าที่ผิดพลาดออกมา ซึ่งไม่สามารถนำไปใช้ได้ ดังนั้น การเขียนโปรแกรมที่มีการคำนวนด้วยการหารทุกครั้ง จำเป็นต้องตรวจสอบว่าตัวหารไม่เป็น 0 ก่อนจึงจะทำการหารได้
Infinite loops:ลูปอนันต์
ข้อกำหนดหนึ่งของการตัดสินว่าวิธีการทำงานใดๆเป็น Algorithm หรือเปล่านั้นคือ การทำงานจะต้องยืนยันได้ว่ามีจุดสิ้นสุด
... i=10; do printf("%d\n",i--); until (i=0); ...
ถ้าโดยปกติแล้ว โปรแกรมข้างต้นนี้ก็จะทำงาน 10 รอบ โดยแต่ละรอบจะพิมพ์ตัวเลขตั้งแต่ 10 จนถึง 1 อย่างละ 1 ตัว แต่ในความจริงแล้ว โปรแกรมจะพิมพ์ 10 หนึ่งครั้งแล้ว ตามด้วย 0 ไปเรื่อยๆ ไม่รู้จบ (ดูที่เงื่อนไขในวงเล็บ) ซึ่งอันนี้เป็นข้อผิดพลาดอีกอันนึงที่เกิดขึ้นได้บ่อยๆคือ การที่โปรแกรมทำงานแบบไม่รู้จบ หรือที่บางคนเรียกกันว่า "ติดลูป" ซึ่งทำให้โปรแกรมพบกับข้อผิดพลาดได้
Arithmetic overflow or underflow:ค่าเกินขอบเขตตัวแปร
int a,b,c; ... a=b+c;
โปรแกรมง่ายๆข้างต้นนี้ จะมีปัญหาใหญ่ ถ้า b+c นั้นมีค่าเกินขอบเขตของตัวแปร int ซึ่งจะทำให้การคำนวนได้ค่าที่ผิดไปจากค่าที่ต้องการ
Exceeding array bounds:ค่าตัวชี้เกินขนาดของตัวแปรArray
int a[SIZE],index,data; ... a[index]=data;
นี่ก็เป็นอีกตัวอย่างนึงที่แสดงให้เห็นได้ว่าการเขียนโปรแกรมสามารถมีบักได้ง่ายมาก โปรแกรมข้างต้นจะทำงานผิดพลาดถ้าindexมีค่ามากกว่าหรือเท่ากับSIZE หรือน้อยกว่า 0 ซึ่งถ้าไม่มีการตรวจสอบที่ดี ก็จะทำให้มี บัก ในโปรแกรมได้
Using an uninitialized variable:ใช้ตัวแปรที่ไม่ได้มีการกำหนดค่า
int a,b,c; a=b+c;
ถ้าไม่ได้กำหนดค่าเริ่มต้นให้ แน่นอนว่าไม่มีใครบอกได้ว่าค่าใน a, b และ c จะเป็นค่าอะไร ซึ่งทำให้มีปัญหาได้ค่าที่ไม่ถูกต้องเวลานำไปใช้ หลายๆท่านอาจจะเถียงว่า compiler เป็นผู้ initial ค่าให้ตัวแปรอยู่แล้ว แต่ก็ไม่ใช่ทุก compiler ดังนั้น programmer จึงควรที่จะ initial ค่าของตัวแปรทุกตัวดัวยตัวเอง
Accessing memory not owned (Access violation):การอ้างค่าจากหน่วยความจำที่ไม่มีสิทธิ์เข้าถึง
.สำหรับคอมพิวเตอร์ที่เราใช้อยู่นั้น มีการแบ่งหน่วยความจำออกเป็นส่วนต่างๆ มีทั้งส่วนที่โปรแกรมสามารถใช้ได้ และส่วนที่Operating Systemเป็นผู้ใช้งาน ซึ่งบางส่วนนี้ถ้าโปรแกรมไปอ้างค่าจากหน่วยความจำเหล่านี้ ก็อาจจะมีปัญหากับระบบได้ ส่วนใหญ่แล้ว โปรแกรมต่างๆจะสามารถอ้างหน่วยความจำได้เฉพาะส่วนที่เป็นเจ้าของเองเท่านั้น ดังนั้นถ้าหากมีการอ้างหน่วยความจำที่ไม่ได้เป็นเจ้าของ หรือไม่มีสิทธิ์เข้าถึงก็อาจจะทำให้ได้ค่าที่ไม่ถูกต้อง หรือไปทำให้ค่าที่เก็บอยู่ในหน่วยความจำส่วนนั่นผิดพลาดได้
Memory leak or Handle leak:หน่วยความจำไม่พอ
Stack overflow or underflow:ปัญหาสแตกล้น และปัญหาข้อมูลในสแตกไม่พอ
Buffer overflow:
int strcpy(char *source,char *target){ int count = 0; while (*source) do{ *(target++)=*(source++); count++; } *target=0; return count; }
โปรแกรมนี้จะมีปัญหาคือ ถ้าจำนวนตัวอักษร ของ source มีมากกว่าจำนวนตัวอักษรที่ target เก็บได้ก็จะทำให้เกิดการ overflow ได้
Deadlock:การแย่งทรัพยากร
Off by one error:
สำหรับข้อมูลแบบArray ปกติแล้วจะมี
Race hazard:
ปัญหาเรื่องเวลา ส่วนใหญ่ปัญหานี้ จะเกิดเมื่อมีการทำงานพร้อมๆกันหลายๆprocessหรือหลายๆthread ตัวอย่างง่ายๆเช่น
for (int i = 0;i<10;i++){ j=i; printf("Copy from i = %d\n",j); }
for (int k = 0;k<10;k++){ j=k; printf("Copy from k = %d\n",j); }
Loss of precision in type conversion:
หลักการลดจำนวนบัก
ถึงแม้ว่าการเีขียนโปรแกรมที่ไม่ให้มีข้อผิดพลาดเลยนั้น เป็นไปได้ยาก แต่การที่จะเขียนโปรแกรมที่สามารถจะหาข้อผิดพลาดที่มีในโปรแกรมได้ง่ายนั้นไม่เป็นสิ่งที่เกินความสามารถ
Layout
สิ่งแรกที่จะทำให้หาข้อผิดพลาดในโปรแกรมได้ง่าย ก็คือต้องเขียนโปรแกรมโปรแกรมให้อ่านง่าย เพื่อที่จะได้สามารถทำความเข้าใจกับโปรแกรมได้ง่าย อย่างเช่น
long power(int x,int y){ long z=1; for (;y--;)z*=x; return z; }
อ่านยากกว่า
long power(int x,int y){ long z=1; for (null;y--;null) z*=x; return z; }
Name
Copy&Paste
Preprocesser
- #define
- #if
- #endif
- #else
- #ifdef
- #ifndef
Trap
กล่าวโดยสรุปแล้ว บัก เกิดเพราะความไม่รอบคอบและความไม่รู้ของโปรแกรมเมอร์ ดังนั้นการลดจำนวนบักที่จำเป็นก็คือ โปรแกรมเมอร์ต้องรอบคอบ และหาความรู้ใหม่ๆเพิ่มเติมอยู่อย่างสม่ำเสมอ