|
fgetc กับเรื่องผิดพลาดที่พบบ่อย
ข้อผิดพลาดที่พบบ่อยในการเขียนโปรแกรมเพื่ออ่าน file โดยใช้ function fgetc คือการใช้ประเภท variable ที่ไม่เหมาะสม มารับค่าจาก function fgetc
fgetc เป็น function ที่ใช้ในการอ่านข้อมูลจาก file มาทีละ byte จนกระทั่งจบ (end-of-file) file เมื่อจบ file หรือเกิด error, fgetc จะ return ค่าเป็น EOF ใน standard C กำหนดค่าของ EOF ให้เป็น integer ที่มีค่าติดลบ แต่โดยทั่วไปเราจะเห็นว่ามีการกำหนด EOF ใน stdio.h ให้มีค่าเป็น -1
เมื่อ fgetc return ข้อมูลเป็น byte บวกกับ EOF อีกหนึ่งค่า variable ที่จะเอาไปรับค่าจาก fgetc ก็ต้องใหญ่พอที่จะรับได้ทั้ง byte และ EOF คือต้องมีขนาดใหญ่กว่า 1 byte และต้องเป็นประเภท signed เพราะ EOF เป็นค่าติดลบ
แต่ผมเห็นว่ามีการใช้ variable ประเภท char ไปรับค่าจาก fgetc กันบ่อยๆ
ใน standard C กำหนดขนาดของ char ไว้ว่าต้องไม่น้อยกว่า 8 bits (1 byte) แต่เราไม่เคยเห็น char มีขนาดใหญ่กว่า 1 byte เลย เรียกว่าใน compiler สมัยใหม่ char มีขนาด 1 byte ซึ่งพอเอา char มารับค่าจาก fgetc จะทำให้ไม่สามารถแยกความแตกต่างระหว่างข้อมูลกับ EOF ได้ ตัวอย่างโปรแกรมผิดๆ ที่เห็นบ่อย char ch; while ((ch = fgetc(fp)) != EOF) { ... } ตัวอย่างนี้ โปรแกรมอาจไม่ออกจาก while loop เลย หรือออกจาก loop ก่อนเวลาอันควรก็ได้ ขึ้นอยู่กับว่าไปเจอข้อมูลอะไร และ char เฉยๆ เป็น signed char หรือ unsigned char เรื่อง char เป็น signed หรือ unsigned ลองอ่านได้จาก entry เก่าของผมเรื่อง Implementation-defined (1)
ลองมาเขียนโปรแกรมทดสอบดู
ขั้นตอนของโปรแกรมทดสอบ สร้าง file ที่มีขนาด 3 bytes byte 1 มีข้อมูล 65 byte 2 มีข้อมูล 66 byte 3 มีข้อมูล 255
เปิด file ที่สร้างขึ้นมา อ่านด้วย fgetc แล้วเอาค่าไปใส่ใน variable ก่อนจะแสดงค่าขึ้นจอ อ่านไปจนกระทั้งเจอ EOF ในกรณีไม่เจอ EOF ก็จะอ่านแค่ 5 ครั้งพอ แล้วปิด file
ทำการอ่าน file โดยใช้ variable ประเภท char, unsigned char, signed char และ int ซึ่งถ้าโปรแกรมทำงานถูก ก็จะอ่านได้แค่ 3 bytes แล้วเจอ EOF
โปรแกรมทดสอบ #include <stdio.h> #define TEST_FILE "test.dat"
int main () { char ch; unsigned char uch; signed char sch; int c, i; FILE *fp;
puts("Create file"); fp = fopen(TEST_FILE, "wb"); if (fp == NULL) { perror("fopen wb"); return -1; } putc(65,fp); putc(66,fp); putc(255,fp); fclose(fp);
puts("\nRead file with char"); fp = fopen(TEST_FILE, "rb"); if (fp == NULL) { perror("fopen rb"); return -1; } for (i = 0; i < 5; i++) { ch = fgetc(fp); printf("%d %s EOF\n", ch, (ch==EOF)? "==" : "!=" ); if (ch == EOF) break; } printf("%d bytes read\n", i); fclose(fp);
puts("\nRead file with unsigned char"); fp = fopen(TEST_FILE, "rb"); if (fp == NULL) { perror("fopen rb"); return -1; } for (i = 0; i < 5; i++) { uch = fgetc(fp); printf("%d %s EOF\n", uch, (uch==EOF)? "==" : "!=" ); if (uch == EOF) break; } printf("%d bytes read\n", i); fclose(fp);
puts("\nRead file with signed char"); fp = fopen(TEST_FILE, "rb"); if (fp == NULL) { perror("fopen rb"); return -1; } for (i = 0; i < 5; i++) { sch = fgetc(fp); printf("%d %s EOF\n", sch, (sch==EOF)? "==" : "!=" ); if (sch == EOF) break; } printf("%d bytes read\n", i); fclose(fp);
puts("\nRead file with int"); fp = fopen(TEST_FILE, "rb"); if (fp == NULL) { perror("fopen rb"); return -1; } for (i = 0; i < 5; i++) { c = fgetc(fp); printf("%d %s EOF\n", c, (c==EOF)? "==" : "!=" ); if (c == EOF) break; } printf("%d bytes read\n", i); fclose(fp);
getchar(); return 0; }
ผมใช้ Visual C++ 2008 Express Edition compile ผลลัพธ์ที่ได้คือ Create file
Read file with char 65 != EOF 66 != EOF -1 == EOF 2 bytes read
Read file with unsigned char 65 != EOF 66 != EOF 255 != EOF 255 != EOF 255 != EOF 5 bytes read
Read file with signed char 65 != EOF 66 != EOF -1 == EOF 2 bytes read
Read file with int 65 != EOF 66 != EOF 255 != EOF -1 == EOF 3 bytes read
พวกที่เอา char กับ signed char มารับ fgetc จะอ่านได้แค่ 2 bytes คือ 65 กับ 66 พอไปเจอ 255 มันจะตีความว่าเป็น -1 เลยกลายเป็น EOF ไป
ส่วน unsigned char เวลาอ่านจนเจอ EOF (-1) พอเอาไปใส่ใน unsigned char จะกลายเป็น 255 ไม่เท่ากับ EOF เลยอ่านทะลุ EOF ไปเป็น 5 bytes
มีแต่ int ที่ทำงานถูกต้อง คืออ่านได้ 3 bytes แล้วเจอ EOF
ปํญหานี้ไม่ได้เกิดกับ fgetc เท่านั้น พวก getc และ getchar ก็เป็นเหมือนกันนะครับ prototype ของ function พวกนี้ (อยู่ใน stdio.h) จะ return ค่าเป็น int ครับ ประมาณนี้ int fgetc (FILE *stream); int getc (FILE *stream); int getchar (void); เพราะฉะนั้นเวลาใช้งานก็เอา int ไปรับไม่ต้องคิดจะประหยัด memory โดยใช้ char แทนหรอกครับ
ผมสงสัยว่าทำไมถึงมีคนเอา char ไปรับค่าจาก fgetc, getc, getchar กัน มันมีที่มาหรือไปเห็นตัวอย่างมาจากไหน textbook เล่มที่ผมใช้ตอนหัดเขียนภาษา C The C programming Language By Brian W. Kernighan and Dennis M. Ritchie. มันก็อธิบายปัญหานี้ใว้อย่างชัดเจน ขนาดว่าพอพูดเรื่อง EOF ก็อธิบายเรื่องนี้เลย หรืออย่าง man page fgetc ของ Unix/Linux และข้อมูลจาก wikipedia ก็พูดถึงปํญหานี้ แล้วทำไมถึงยังทำผิดกัน
นานมาแล้วผมเคยเห็นตัวอย่างผิดๆ เรื่องนี้ในหนังสือภาษาไทย พอมีโอกาสไปร้านหนังสือ ก็เลยไปสำรวจหนังสือสอนภาษา C ที่เป็นภาษาไทยดู
แล้วก็ถึงบางอ้อ hit rate 100% เลยครับ ไม่เชื่อไปลองดูที่ร้านได้
![](//www.bloggang.com/emo/emo23.gif)
Create Date : 15 สิงหาคม 2552 |
Last Update : 15 สิงหาคม 2552 22:08:27 น. |
|
2 comments
|
Counter : 2133 Pageviews. |
![](../images/bg-follower.png) |
|
|
โดย: Kaspersky Internet Security2010 IP: 58.136.28.156 วันที่: 18 สิงหาคม 2552 เวลา:12:06:08 น. |
|
|
|
โดย: motokop วันที่: 1 มกราคม 2554 เวลา:0:52:37 น. |
|
|
|
| |
|
|
ทางบริษัทจึงอยากเรียนเชิญบล็อกเกอร์ที่มีชื่อเสียงในประเทศไทยมาร่วมงาน เนื่องจากเป็นผู้ที่ใช้อินเตอร์เน็ทในการทำงานอย่างมาก มะลิเห็นว่าคุณ zkaruเป็นบลอกเกอร์ที่มีความสนใจในด้านไอที คอมพิวเตอร์จึงอยากจะเรียนเชิญ คุณให้เกียรติมาร่วมงานนี้ด้วยกันค่ะ
ซึ่ง ภายในงานจะมีบูธ K-Klub ให้ร่วมลงทะเบียนเป็นแฟนคลับแคสเปอร์สกี้ บูธ K-lab นำแฮนดี้ไดฟ์มาสแกนไวรัสได้ฟรี มี Education Zone ให้ความรู้เกี่ยวกับไวรัส นอกจากสาระที่ได้รับแล้ว ยังได้รับความบันเทิงจากโชว์extream jumping ปิดท้ายด้วย beat box b-boy ที่เป็นแชมป์ระดับเอเชียด้วยค่ะ
สำหรับงาน จะจัดขึ้นวันที่ 28 สิงหาคม 2552 ณ ลานสยามดิสคอฟเวอร์รี่ เริ่มลงทะเบียนในเวลา16.00 น. ที่จุดลงทะเบียนค่ะ แจ้งว่าเป็นบล็อกเกอร์นะคะ
ดังนั้นจึงอยากให้คุณzkaruรบกวนติดต่อกลับมะลิที่ Sitaporn@piton.biz โทร 085-150-5269 หรือ ติดต่อแนน watchariya@piton.biz โทร 089-779-5732 เพื่อยืนยันการเข้าร่วมงาน หรือหากต้องการสอบถามข้อมูลเพิ่มเติม ขอบคุณค่ะ
ปล. งานนี้ฟรี ไม่เสียค่าใช้จ่ายใดๆทั้งสิ้นค่ะ