เรื่องที่อยากเขียน ... เรื่องที่พยายามเขียน ...
Group Blog
 
All blogs
 
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% เลยครับ
ไม่เชื่อไปลองดูที่ร้านได้








Create Date : 15 สิงหาคม 2552
Last Update : 15 สิงหาคม 2552 22:08:27 น. 2 comments
Counter : 2133 Pageviews.

 
สวัสดีค่ะ มะลินะคะ จาก Piton communication บริษัท PR agency ให้แก่ Kaspersky บริษัทที่มีโปรดักส์ แอนตี้ไวรัสจากรัสเซีย กำลังจะจัดงาน เปิดตัวproduct ใหม่ Kaspersky Internet Security2010 ที่ลานสยามดิสคอฟเวอร์รี่ ในวันที่ 28 สิงหาคมนี้
ทางบริษัทจึงอยากเรียนเชิญบล็อกเกอร์ที่มีชื่อเสียงในประเทศไทยมาร่วมงาน เนื่องจากเป็นผู้ที่ใช้อินเตอร์เน็ทในการทำงานอย่างมาก มะลิเห็นว่าคุณ 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 เพื่อยืนยันการเข้าร่วมงาน หรือหากต้องการสอบถามข้อมูลเพิ่มเติม ขอบคุณค่ะ
ปล. งานนี้ฟรี ไม่เสียค่าใช้จ่ายใดๆทั้งสิ้นค่ะ


โดย: Kaspersky Internet Security2010 IP: 58.136.28.156 วันที่: 18 สิงหาคม 2552 เวลา:12:06:08 น.  

 
สวัสดีปีใหม่ครับ


โดย: motokop วันที่: 1 มกราคม 2554 เวลา:0:52:37 น.  

ชื่อ :
Comment :
  *ใช้ code html ตกแต่งข้อความได้เฉพาะสมาชิก
 

zkaru
Location :
กรุงเทพฯ Thailand

[Profile ทั้งหมด]

ฝากข้อความหลังไมค์
Rss Feed
Smember
ผู้ติดตามบล็อก : 2 คน [?]




เรื่องที่อยากเขียน ... เรื่องที่พยายามเขียน ...
Friends' blogs
[Add zkaru's blog to your web]
Links
 

 Pantip.com | PantipMarket.com | Pantown.com | © 2004 BlogGang.com allrights reserved.