เรื่องที่อยากเขียน ... เรื่องที่พยายามเขียน ...
Group Blog
 
All blogs
 

feof กับ ferror

function ที่ใช้อ่าน file ใน standard library ของภาษา C มีอยู่ด้วยกันหลาย function
แต่ function เหล่านี้จะไม่สามารถแยกความแตกต่างระหว่าง end-of-file กับการเกิด error ได้
ตัวอย่างของ function เหล่านี้คือ

int fgetc (FILE *stream);
int getc (FILE *stream);
int getchar (void);

จะ return EOF เมื่อเจอ end-of-file หรือเมื่อเกิด error

char *fgets (char *s, int n, FILE *stream);
char *gets (char *s);

จะ return NULL เมื่อเจอ end-of-file หรือเมื่อเกิด error

int fscanf (FILE *stream, char *format, ... );
int scanf (char *format, ... );

จะ return EOF เมื่อเจอ end-of-file หรือเมื่อเกิด error

size_t fread (void *ptr, size_t size, size_t nitems, FILE *stream);
จะ return ค่าไม่เท่ากับ nitems เมื่อเจอ end-of-file หรือเมื่อเกิด error

การที่จะแยกให้ได้ว่าเกิด end-of-file หรือเกิด error ต้องใช้ feof กับ ferror ครับ
int feof (FILE *stream);
int ferror (FILE *stream);


เมื่อเกิด error ในการอ่าน FILE *stream
การเรียกใช้ ferror (stream) จะ return ค่าเป็น non-zero
ทำนองเดียวกัน เมื่ออ่าน FILE *stream จนเจอ end-of-file
การเรียกใช้ feof (FILE *stream) จะ return ค่าเป็น non-zero
function ทั้งสองจะมีลักษณะเหมือนกันคือ
ต้องเกิด เหตุ ขึ้นเสียก่อน แล้วถึงจะทดสอบเจอ
หาก เหตุไม่เกิด ก็ทดสอบไม่เจอ

สำหรับ ferror คงไม่ค่อยมีปัญหา (อาจเป็นเพราะไม่ค่อยเห็นใช้กัน)
แต่ feof นี่ผมเห็นใช้ผิดกันบ่อยๆ
เพราะว่ามันต้องอ่านจนเจอ end-of-file ถึงจะใช้ feof ทดสอบได้
แต่ที่เห็นใช้กันผิด คือไปทดสอบ feof ก่อน แล้วค่อยไปอ่าน stream เช่น

   while (!feof(fp)) {
      c = getc(fp);
      putchar(c);
   }


เพราะว่าถึงอ่าน byte สุดท้ายของ file ไปแล้ว คืออยู่ที่ท้าย file ไม่มีข้อมูลแล้ว
แต่ว่าตัว library function มันยังไม่รู้นะครับว่าไม่มีข้อมูลแล้ว
เราต้องสั่งให้อ่านต่อไป มันถึงจะรู้ว่าไม่มีข้อมูลแล้ว เจอ end-of-file แล้ว
feof ถึงจะทดสอบเจอ

มาทดสอบกันโดยสร้าง file ที่มีหนึ่งตัวอักษร
แล้วลองอ่าน file โดยใช้โปรแกรมที่ผิดแบบข้างบน
เปรียบเทียบกับโปรแกรมที่เขียนถูก

#include <stdio.h>
#define TEST_FILE "test.dat"

int main ()
{
   int c;
   FILE *fp;

   puts("Create file contains one character");
   fp = fopen(TEST_FILE, "w");
   if (fp == NULL) {
      perror("fopen w");
      return -1;
   }
   putc('A',fp);
   fclose(fp);

   puts("Bad feof program");
   fp = fopen(TEST_FILE, "r");
   if (fp == NULL) {
      perror("fopen r");
      return -1;
   }
   while (!feof(fp)) {
      c = getc(fp);
      printf("%c(%d)\n", c, c);
   }
   fclose(fp);

   puts("Good program");
   fp = fopen(TEST_FILE, "r");
   if (fp == NULL) {
      perror("fopen r");
      return -1;
   }
   while ((c = getc(fp)) != EOF)
      printf("%c(%d)\n", c, c);
   fclose(fp);

   puts("End");
   getchar();
   return 0;
}


ผลลัพธ์ที่ได้คือ
Create file contains one character
Bad feof program
A(65)
(-1)
Good program
A(65)
End


จะเห็นว่า feof จะทดสอบไม่เจอ end-of-file มันเลยอ่านเกินไปตัวนึง

แล้วควรใช้งาน feof ยังไง
คำตอบคือ ให้ใช้เท่าที่จำเป็น

จะเห็นว่าในโปรแกรมทดสอบอันที่ถูกของผม ไม่ใช้ feof ด้วยซ้ำ
เพราะมันไม่จำเป็นต้องใช้
ที่ผิดกันบ่อยคือใช้ feof เป็น loop condition
ทั้งที่จริงๆเราก็ loop อ่านจนมันอ่านไม่ได้แหละครับ
ไม่ว่าจะอ่านไม่ได้เพราะ end-of-file หรือ error
ไม่เห็นต้องใช้ feof เลย
หลังออกจาก loop แล้วถ้าอยากรู้ว่า error หรือเปล่าค่อยไปใช้ ferror เช่น

   while ((c = getc(fp)) != EOF)
      printf("%c(%d)\n", c, c);
   if (ferror(fp))
      perror("getc");






สรุปว่าใช้แบบพอเพียงครับ







 

Create Date : 12 กันยายน 2552    
Last Update : 12 กันยายน 2552 21:27:08 น.
Counter : 1392 Pageviews.  

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 น.
Counter : 811 Pageviews.  

String Constant


ใน entry นี้จะพูดถึงเรื่องที่ C programmer รู้จักกันดีอีกเรื่องหนึ่ง
คือ string constant ครับ



string constant ก็คือการนำเอา character constant มาเรียงต่อๆกัน
โดยมีเครื่องหมาย double quote " ปิดหัวปิดท้าย

จำนวน character ที่มาเรียงกันจะมีกี่ตัวก็ได้
ถ้าไม่มีเลยสักตัวจะเรียกว่า empty string
แต่ไม่ว่าจะมีกี่ตัว compiler จะเติม null character '\0' ปิดท้ายให้เสมอ



ประเภทของ string constant ก็จะเหมือนกับ character constant คือมีสองแบบ
แบบ char ธรรมดา กับแบบ wide character (wchar_t)
แต่ที่จะพูดถึงใน entry นี้ จะเป็นแบบ char ธรรมดาครับ


ตัวอย่างของ string constant คือ
"This is the String"
"" /* empty string */


เนื่องจาก string constant คือการนำเอา character constant มาเรียงต่อๆกัน
ดังนั้นเราสามารถใส่ escape code แบบเดียวกับ character constant ตามรูป




เราสามารถเอา string constant หลายอันมาต่อกันเป็น string constant อันเดียว
compiler จะเติม null character ปิดท้ายหลังจากรวมกันแล้วเท่านั้น
ตัวอย่างต่อไปนี้จะให้ผลลัพธ์แบบเดียวกัน

ตัวอย่างที่ 1
printf("This "
    "is "
    "my "   "string\n");

ตัวอย่างที่ 2
printf("This is my \
string\n");

ตัวอย่างที่ 3
printf("This is my string\n");

การต่อ string ด้วยเครื่องหมาย \ ในตัวอย่างที่ 2
หลังเครื่องหมาย \ ต้องขึ้นบรรทัดใหม่ทันที ห้ามมีตัวอักษรใดๆ



การต่อ string ไม่ได้มีจุดมุ่งหมายแค่เอา string constant หลายอันมาต่อกันเท่านั้น
บางครั้งเป็นการป้องกัน compiler เข้าใจผิด ลองมาดูตัวอย่าง

สมมุติว่าเราต้องการสร้าง string constant ที่มี character สองตัว
character ตัวแรกคือ '\01' (เลขฐานแปดค่า 1)
ส่วนตัวที่สองคือ '2' (ASCII character เลขสอง)

ถ้าสร้าง string constant โดยนำ character ทั้งสองมาเรียงกันเฉยๆ
จะได้ "\012"
compiler จะตีความว่า string constant นี้มี character ตัวเดียวคือ '\012'
เป็น octal number เลขฐานแปด
ซึ่งไม่ได้เป็นไปตามที่เราต้องการ

กรณีเราอาจจะทำเป็นสอง string มาต่อกัน
แบบนี้ "\01" "2"
compiler จะตีความเป็น string constant ที่มีสอง character อย่างที่เราต้องการ



ขนาดของ string constant จะเท่ากับขนาดของจำนวน character constant ที่มีอยู่
บวกกับ null character ที่ compiler เติมปิดท้ายให้
ส่วนความยาวของ string ได้จากการนับจำนวน character จนถึง null character ไม่รวม null นะครับ
ขนาดของ string constant หาได้จาก sizeof
ส่วนความยาวของ string ได้จากการเรียกใช้ function strlen ใน standard C library

string sizeof value strlen value
"" sizeof "" 1 strlen("") 0
"xyz" sizeof "xyz" 4 strlen("xyz") 3
"xyz\0" sizeof "xyz\0" 5 strlen("xyz\0") 3

"xyz\0" จะมี null character ปิดท้ายสองตัว
ตัวแรกมาจากการกำหนด \0 ส่วนตัวที่สอง compiler เติมให้



string constant ที่เหมือนกัน ไม่จำเป็นต้องอยู่ใน memory ที่เดียวกัน
ถือว่าเป็น implementation defined ครับ
โปรแกรมด้านล่างถ้าไป compile และรันด้วย compiler ต่างกันก็อาจได้ผลไม่เหมือนกัน
#include <stdio.h>
int main() {
char *s1 = "abcxyz";
char *s2 = "abcxyz";
if (s1 == s2) printf("s1 and s2 point to the same string\n");
else printf("s1 and s2 point to the different string\n");
return 0;
}



เรื่องสุดท้ายคือ การแก้ไขค่าใน string constant จะถือเป็น undefined behavior ครับ

compiler อาจจะจัดให้ string constant อยู่ใน memory ส่วนที่เป็น read-only
หรือ memory ส่วนที่มีระบบป้องกันการแก้ไขค่าในขณะที่รันโปรแกรม
การไปแก้ไขค่าใน string constant เลยอาจทำให้โปรแกรมทำงานผิด หรือตายได้

อย่างไรก็ตาม ความสามารถในการป้องกัน memory แบบนี้
ขึ้นกับ OS หรือ platform
หาก OS หรือ platform ไม่มีระบบป้องกัน
การไปแก้ไขค่าใน string constant ก็จะทำได้
มันเลยไม่แน่นอน เป็น undefined behavior


สรุปว่าไม่ควรแก้ไขค่าใน string constant ครับ
ชื่อมันก็บอกอยู่แล้วว่าเป็น constant แล้วยังจะไปแก้ค่ามันอีก







 

Create Date : 13 กันยายน 2551    
Last Update : 14 กันยายน 2551 9:01:13 น.
Counter : 507 Pageviews.  

Character Constant

entry นี้เป็นเรื่องของ character constant ครับ

คนเขียนภาษา C ต้องรู้จัก character constant ดีกันทุกคน
เพราะต้องเคยเขียนและคุ้นเคยกันมาตั้งแต่โปรแกรมแรกๆแล้ว
น่าจะเป็นเรื่องง่ายๆ
ลองมาดูว่า เรื่อง character constant จะง่ายแค่ไหน

character constant แปลตรงตัวคือ ค่าคงที่แบบตัวอักษร
มีทั้งแบบ character ธรรมดา
หนึ่ง character ใช้หนึ่ง char หรือหนึ่ง byte ในการเก็บ
หรือแบบ wide character
หนึ่ง wide character จะใช้หนึ่ง wchar_t ซึ่งมีขนาดหลาย byte ในการเก็บ
wchar_t จะเป็นกี่ byte ต้องไปดูใน header file stddef.h

ที่พูดไปเป็นแบบ single character constant นะครับ
ยังมีแบบ multi-character constant ให้ปวดหัวอีก

อย่างไรก็ตาม ที่ใช้กันมากที่สุด
จะเป็นแบบ single character constant แบบ char ธรรมดา
ใน entry นี้เลยจะพูดถึง character constant แบบนี้




รูปแบบของ single character constant จะมีตัวอักษรหนึ่งตัวอยู่ระหว่างเครื่องหมาย ' (apostrophe) สองตัว
เช่น 'a', 'A', '0' เป็นต้น
ความหมายของค่าคงที่เหล่านี้
ก็คือค่าของ ตัวอักษร ที่อยู่ระหว่างเครื่องหมาย ' นั่นเอง

เช่น ตัวอักษร a เล็ก ในระหัส ASCII มีค่าเป็น 97 หรือ 0x61
ดังนั้น 'a' ก็คือค่า 97 หรือ 0x61 นั่นเอง
แทนที่จะเขียนใน source code เป็น
int c = 97;
ซึ่งอ่านแล้วไม่รู้ว่า 97 เป็นค่าอะไร ก็เขียนเป็น
int c = 'a';
จะทำให้เข้าใจได้ง่ายกว่า




นอกจาก ตัวอักษรหนึ่งตัวระหว่างเครื่องหมาย ' แล้ว เรายังสามารถใส่ escape code
สำหรับค่าที่ ไม่สามารถ หรือ ไม่สะดวก ในการเขียนลงใน source code
การใส่ escape code ก็ใช้เครื่องหมาย \ แล้วตามด้วย character ตาม table





escape code ด้านบนมีเรื่องน่าสนใจอยู่หลายเรื่อง




เรื่องแรกคือ octal number \ooo
octal number คือเลขฐานแปด
o คือตัวเลข 0 ถึง 7
\ooo หมายถึงมีเลขฐานแปดได้ 1 หรือ 2 หรือ 3 ตัว เช่น
'\7' หมายถึงค่า 7
'\10' หมายถึงค่า 8 (10 ฐานแปด คือ 8 ในฐานสิบ)
'\377' หมายถึงค่า 255 (377 ฐานแปด คือ 255 ในฐานสิบ)
แต่
'\09' จะเป็น multi-character constant คือมี 2 character
เพราะ 9 ไม่ใช่เลขฐานแปด
character ตัวแรกคือ '\0'
และตัวที่สองคือ '9'




เรื่องต่อมาคือ hex number \xhh
hex number คือเลขฐานสิบหก
h คือตัวเลข 0 ถึง 9 และ A ถึง F หรือ a ถึง f
\xhh หมายถึงมี hex number ตั้งแต่หนึ่งตัวขึ้นไป
ไม่จำกัดว่ามากสุดต้องเป็นสองตัว

แต่จำกัดที่ค่าครับ

ทั้ง octal number และ hex number
ต้องมีค่าไม่เกิน ค่าที่จะเก็บได้ใน unsigned char
พูดง่ายๆคือ อย่าให้เกินค่าสูงสุดของ unsigned char
ค่าสูงสุดของ unsigned char ดูได้จาก UCHAR_MAX
ซึ่ง define อยู่ใน header file limits.h
หาก character constant มีค่าเกิน UCHAR_MAX
จะถือว่าเป็น undefined behavior
คือปล่อยให้ compiler ตัดสินเองว่าจะทำยังไง




เรื่องต่อมาคือ ตัวอักษรที่ตามหลังเครื่องหมาย \
ต้องเป็นไปตามที่มีใน table ด้านบนเท่านั้น
ถ้ามีตัวอื่น เช่น

   n = '\c';
   x = '\=';


จะถือว่าเป็น undefined behavior
ปล่อยให้ compiler ตัดสินเอง อีกแล้ว




เรื่องต่อมาคือ ? กับ "
สำหรับ character constant สองตัวนี้ดูเหมือนจะเป็น option
คือจะมีเครื่องหมาย \ หรือไม่มีก็ได้ เช่น
c = '\?'; จะเหมือนกับ c = '?';
และ c = '\"'; จะเหมือนกับ c = '"';
ซึ่งดูเหมือนไม่มีประโยชน์

แล้วกำหนดขึ้นมาทำไม
คำตอบคือ " จะใช้กับ string constant ซึ่งจะได้อธิบายในโอกาสต่อไป
ส่วน ? จะใช้ในเรื่อง trigraph

trigraph คืออะไร

ระบบ computer บางระบบมี character set จำกัดมาก (เดี๋ยวนี้คงหาดูยากแล้ว)
ไม่มี character สำหรับเครื่องหมายพวกนี้
# ^ [ ] | { } ~
หรือบางทีไม่มีวิธีจะ key หรือ input character พวกนี้

แล้วจะเขียนโปรแกรมภาษา C กันยังไง
ก็เลยกำหนดให้เอา character 3 ตัวที่มีอยู่
มาใช้แทน character ที่ไม่มี หรือที่ key ไม่ได้
เรียก character 3 ตัวนี้ว่า trigraph (สาม graph)
เวลาเขียนโปรแกรมก็ใช้ trigraph แทน
หน้าตาของ trigraph จะเป็นแบบนี้


ประมาณว่ามีโปรแกรมแบบนี้
{
   char a[10];
   a[0] = 1;
}

ก็ต้องเขียนเป็น
??<
   char a??(10??);
   a??(0??) = 1;
??>


ตอน compile โปรแกรม
compiler จะทำการแปลง trigraph พวกนี้
ไปเป็น internal code หรือ internal character set ก่อน
การแปลงจะทำเหมือน search and replace ตรงๆ
ไม่มีการตีความอะไรทั้งสิ้น
แล้วค่อย compile ตามปรกติต่อไป

ด้วยเหตุที่ trigraph จะขึ้นต้นด้วย ?? เสมอ
หากเราไม่อยากให้ compiler เข้าใจผิดเกี่ยวกับ trigraph
เวลาที่มี string constant หรือ character constant
ก็ให้ใช้ \? แทน ? ก็จะไม่เคยเกิด ?? เลย
เป็นการป้องกันความสับสนครับ

แต่โอกาสจะได้เจอ trigraph นี่ น้อยมาก
เรียกว่า ได้ยินแต่ชื่อ ไม่เคยเห็นตัวเป็นๆ ครับ




character constant นี่ชื่อเป็น character ก็จริง แต่มี type เป็น int นะครับ
ดูตัวอย่างโปรแกรม

#include <stdio.h>
int main() {
   printf("%d\n",sizeof('a'));
   return 0;
}


โปรแกรมด้านบน ตั้งชื่อเป็น t1.c ไป compile ด้วย Microsoft VC++
เวลารันจะได้ผลลัพธ์เป็น
4
เพราะ sizeof('a') ก็คือ sizeof(int) นั่นเอง
ซึ่ง int บน windows 32 bits มีขนาดเป็น 4 bytes

แต่ถ้าเป็นภาษา C++
character constant จะมี type เป็น char ครับ
ดังนั้น โปรแกรมเดียวกัน ปรับเป็น C++ ซักหน่อย
โดยตั้งชื่อเป็น t2.cpp ไป compile ด้วย Microsoft VC++ ตัวเดิม
เวลารันจะได้ผลลัพธ์เป็น
1
เพราะ sizeof('a') จะเป็น sizeof(char) ซึ่งมีขนาดเป็น 1 byte

อันนี้เป็นข้อแตกต่างที่สำคัญ ระหว่างภาษา C กับ C++
ต้องระวังหน่อยนะครับ อย่าให้ compiler หลอกเอาได้
ท่องไว้เลยครับ
C ไม่เท่ากับ C++
C ไม่เท่ากับ C++
C ไม่เท่ากับ C++
C ไม่เท่ากับ C++
C ไม่เท่ากับ C++
C ไม่เท่ากับ C++



สรุปว่าเรื่อง character constant ที่น่าจะคุ้นเคยกันดี
เพราะใช้กันมานาน เขียนกันมานาน
ก็อาจจะผิดพลาด เพราะไม่เข้าใจข้อกำหนดของ standard C ได้เหมือนกัน


เห็นไหมครับว่า character constant นี่ง่ายนิดเดียว








แก้ที่ผิด+ปรับข้อความ




 

Create Date : 06 กันยายน 2551    
Last Update : 21 พฤษภาคม 2553 17:05:55 น.
Counter : 552 Pageviews.  

Automatic Variable

ใน entry ที่แล้วผมยกตัวอย่าง order of evaluation
ว่าเป็น undefined behavior ที่น่าจะเป็นปัญหามากที่สุด
เพราะเข้าใจยาก หาที่ผิดยาก แล้วก็แก้ไขได้ยากด้วย

entry นี้มาดูเรื่องที่ง่ายลงมาหน่อย
เป็น undefined เหมือนกัน
แต่เป็น undefined value ครับ

เรื่องที่จะเล่าคือ ค่าเริ่มต้นของ automatic variable ครับ

automatic variable หรือ auto variable คือ
variable ที่ไม่ใช่ static หรือ extern
ซึ่งถูก declare อยู่ภายใน function ครับ
ตัวอย่าง เช่น

int function() {
int x, y; /* อันนี้เป็น automatic variable */
static int seed; /* อันนี้เป็น static variable */
extern int errno; /* อันนี้เป็น extern variable */
...
return 0;
}

เมื่อมีการเรียกใช้ function
compiler จะ allocate ที่สำหรับ automatic variable
โดยให้อยู่ใน memory ส่วนที่เป็น stack
พอโปรแกรมทำงานจบและ return ออกจาก function
จะมีการคืน memory ใน stack ทำให้ variable พวกนี้หายไป หรือ access ไม่ได้
ด้วยเหตุที่ variable พวกนี้เกิดพร้อมกับการเรียกใช้ function
และหายไปหลังจากจบ function
จึงถูกเรียกว่า automatic variable หรือ auto variable ครับ

บางทีก็เรียก auto variable ว่า local variable
เพราะขอบเขตการใช้งานของ variable พวกนี้จะอยู่ภายใต้ block ที่มันสังกัดอยู่
block นั้นจะเริ่มจากเครื่องหมาย { ไปจนถึงเครื่องหมาย }
พอออกนอก block ก็จะไม่เห็น variable ที่ declare ใน block แล้วครับ
variable พวกนี้เลยมีอีกชื่อหนึ่งว่า local variable
คืออยู่ประจำ block นั่นเอง

standard C ล่าสุดคือ C99 ครับ ออกมาเมื่อปี 1999
standard ก่อนหน้านี้ กำหนดว่าการ declare auto variable
จะทำได้เฉพาะที่ส่วนบนของ block หลังจากเครื่องหมาย {
และก่อนที่จะมี statement แรกเท่านั้น
แต่ใน standard C99 จะกำหนดให้ declare ที่ไหนใน block ก็ได้ครับ
ดู code ตัวอย่าง

{ /* block เริ่มที่นี่ */
int i;
i = 1; /* statement แรก */
int j; /* standard compiler ก่อน C99 จะ error ที่นี่ */
j = 2;
...
} /* block จบที่นี่ */

ค่าเริ่มต้นของ auto variable จะเป็น undefined value คือเป็นอะไรก็ได้
เพราะตอน compiler allocate variable ใน stack
ค่าของ memory ใน stack เคยเป็นอะไรอยู่ ก็เป็นอยู่แบบนั้น
auto variable เลยมีค่าเป็นค่าเดิมของ memory ใน stack
คือเป็นอะไรก็ไม่รู้

โดยปรกติการใช้งาน auto variable ต้องตั้งค่าเริ่มต้นก่อน
ไม่งั้นค่าที่ มั่วๆ เป็นอะไรก็ไม่รู้ อาจทำให้โปรแกรมทำงานผิดได้
โดยเฉพาะพวก pointer ถ้ามันชี้ไปที่ไหนก็ไม่รู้
แล้วเราดันไปใช้มัน ทำโปรแกรมตายมาเยอะแล้วครับ
เรียกได้ว่า เป็นข้อผิดพลาดที่พบได้บ่อยทีเดียว

แล้วทำไม standard C ถึงไม่กำหนดให้ compiler
ทำการตั้งค่าเริ่มต้นของ auto variable ไปเลย

เหตุผลคือ เพื่อประสิทธิภาพ เพื่อความเร็วครับ

auto variable ในแต่ละ function จะมีขนาดรวมกันใหญ่แค่ไหนก็ได้
จากไม่กี่ byte จนเป็นแสนเป็นล้าน byte ก็มี
ถ้าทุกครั้งที่เรียกใช้ function แล้วต้องตั้งค่าเริ่มต้นให้กับ variable ทั้งหมด
โปรแกรมคงทำงานช้าลงไปมาก
เลยกำหนดให้คนเขียนโปรแกรมทำเอง compiler ไม่ทำให้ครับ

และบางทีคนเขียนโปรแกรมอาจไม่ต้องการตั้งค่าเริ่มต้นให้กับทุก variable
คือทำเฉพาะบาง variable
หรือต้องการทำเฉพาะบางส่วนของ variable
เช่นพวกที่เป็น array, struct หรือ union
หรือมีเฉพาะบางเงื่อนไขเท่านั้น ถึงจะทำ
เรียกได้ว่าเลือกทำเท่าที่จำเป็นครับ ไม่ได้บังคับ

ทีนี้จะทำอย่างไร compiler ไม่ยอมทำให้
แต่ถ้าคนเขียนโปรแกรมลืมทำ โปรแกรมก็ทำงานผิด
ก็อย่างที่รู้กันว่า หาที่ผิดในภาษา C มันยากขนาดไหน

จริงๆแล้ว compiler ส่วนใหญ่ก็ใหู้ตัวช่วยมาด้วยครับ
คือ compiler จะมี option ให้ทำการเตือน
ถ้ามีการใช้ auto variable โดยที่ยังไม่ได้ตั้งค่า
ไม่ใช่เตือนเฉพาะเรื่องนี้เท่านั้น
เตือนได้สารพัดเรื่องแหละครับ

ดังนั้นจึงควรใช้ warning option พวกนี้ให้เป็นประโยชน์
ใช้ option สูงสุดได้ยิ่งดี เช่น
VisualC 6.0 ก็ใช้ /W4
GNU gcc ก็ใช้ -Wall

เวลา compile แล้วมี warning message
ก็ควรอ่านให้ละเอียด ทำความเข้าใจว่าเกิดอะไรขึ้น
อย่าไปคิดว่าเป็นแค่ warning ไม่เป็นไร

แต่อย่าคาดหวังว่า compiler จะ warning ได้ถูกต้องเสมอไปนะครับ
บางที compiler ก็ถูกหลอกได้เหมือนกัน




 

Create Date : 05 กันยายน 2551    
Last Update : 11 กันยายน 2551 21:19:42 น.
Counter : 541 Pageviews.  

1  2  

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

[Profile ทั้งหมด]

ให้ทิปเจ้าของ Blog [?]
ฝากข้อความหลังไมค์
Rss Feed
Smember
ผู้ติดตามบล็อก : 1 คน [?]




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

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