|
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 : 3526 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 : 2097 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 : 1331 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 : 1653 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 : 1101 Pageviews. |
| |
|
|
|
|
| |
|
|