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

Undefined behavior (1)


undefined behavior เป็นเรื่องอิสระภาพของ compiler ครับ
เป็นเรื่องที่ compiler มีอิสระ จะทำอย่างไรก็ได้ และไม่จำเป็นต้องระบุด้วยครับ
แตกต่างจาก implementation-defined ซึ่ง compiler ต้องระบุให้ชัดเจนว่าจะทำอย่างไร

undefined behavior ที่น่าจะเป็นปัญหามากที่สุดคือ order of evaluation ครับ
เพราะจะทำให้ผิดง่าย หาที่ผิดยาก ประมาณว่าถึงรู้ว่ามีที่ผิด ถ้าต้องแก้ให้ถูกทั้งหมดนี่เรื่องใหญ่เลย

ปัญหาจาก order of evaluation เรื่องแรกที่จะยกตัวอย่างคือ
การส่ง parameter ขณะเรียกใช้ function
ลองดู code ตัวอย่าง

#include <stdio.h>
int main() {
int i=0;
printf("%d,%d\n",++i,++i);
return 0;
}

code ข้างบนถ้าไป compile และรันด้วย
Microsoft VC++ บน Windows CPU intel
GNU gcc บน Linux CPU intel
จะได้ผลลัพธ์เป็น
2,1
แต่ถ้าไป compile และรันด้วย
GNU gcc บน Solaris CPU SPARC
ก็จะได้ผลลัพธ์เป็น
1,2
จะเห็นได้ว่า ได้ผลลัพธ์ไม่เหมือนกันครับ

statement ที่เป็นปัญหาคือ
printf("%d,%d\n",++i,++i);
ใน statement นี้เป็นการเรียก function printf โดยมี
"%d,%d\n" เป็น parameter ตัวแรก
++i เป็น parameter ตัวที่สอง
++i เป็น parameter ตัวที่สาม
ในการส่ง parameter ไปให้ function นั้น compiler มีอิสระที่จะ evaluate ค่าที่จะส่ง
โดยทำจาก ซ้ายไปขวา หรือ ขวาไปซ้าย ก็ได้

ลองมาดูว่าเกิดอะไรขึ้น
ก่อนหน้า statement นี้ i จะมีค่าเป็น 0

ถ้า compiler เลือกที่จะ evaluate ค่าที่จะส่งไปให้ printf จาก ซ้ายไปขวา
parameter ตัวแรกจะเป็น "%d,%d\n" ซึ่งเป็นค่าคงที่แบบ string
parameter ตัวที่สองจะเป็น 1 เพราะ i เป็น 0 อยู่ พอทำ ++i เลยได้ค่าเป็น 1 และ i กลายเป็น 1
parameter ตัวที่สามจะเป็น 2 เพราะ i เป็น 1 อยู่ พอทำ ++i เลยได้ค่าเป็น 2 และ i กลายเป็น 2
เลยได้ผลประมาณว่าส่ง 1 กับ 2 ไปให้ printf ทำ "%d,%d\n"
printf("%d,%d\n",1,2);
ผลออกมาเลยเป็น
1,2

ถ้า compiler เลือกที่จะ evaluate ค่าที่จะส่งไปให้ printf จาก ขวาไปซ้าย
parameter ตัวที่สามจะเป็น 1 เพราะ i เป็น 0 อยู่ พอทำ ++i เลยได้ค่าเป็น 1 และ i กลายเป็น 1
parameter ตัวที่สองจะเป็น 2 เพราะ i เป็น 1 อยู่ พอทำ ++i เลยได้ค่าเป็น 2 และ i กลายเป็น 2
parameter ตัวแรกจะเป็น "%d,%d\n" ซึ่งเป็นค่าคงที่แบบ string
เลยได้ผลประมาณว่าส่ง 2 กับ 1 ไปให้ printf ทำ "%d,%d\n"
printf("%d,%d\n",2,1);
ผลออกมาเลยเป็น
2,1

เห็นมั๊ยครับว่า ส่งจากซ้ายไปขวา กับ ขวาไปซ้าย จะให้ผลไม่เหมือนกัน
นี่คือสาเหตุที่ทำให้ printf ออกมาไม่เหมือนกันครับ

อีกตัวอย่างหนึ่งของปัญหาจาก order of evaluation คือการ assign ค่าครับ
ลองดู code นี้
int a[10], i=0;
a[i]=i++;

statement ที่เป็นปัญหาคือ
a[i]=i++;
อันนี้จะเป็นปัญหาของการที่ compiler ทำการ evaluate ข้างซ้าย หรือ ข้างขวา ของเครื่องหมาย =
ว่าจะทำข้างไหนก่อน

ลองมาดูว่าเกิดอะไรขึ้น
ก่อนหน้า statement นี้ i จะมีค่าเป็น 0

ถ้า compiler เลือกที่จะ evaluate ข้างซ้ายก่อน
ข้างซ้ายคือ a[i] และ i เป็น 0 อยู่
ข้างซ้ายก็จะกลายเป็น a[0]
ส่วนข้างขวาคือ i++ จะได้ค่าเป็น 0 เพราะ i เป็น 0 ก่อนจะทำ i++ ให้ i กลายเป็น 1
จับข้างซ้ายมา assign กับข้างขวา ผลคือ
a[0]=0
คือเอาค่า 0 ไปใส่ที่ a[0]

ถ้า compiler เลือกที่จะ evaluate ข้างขวาก่อน
ข้างขวาคือ i++ จะได้ค่าเป็น 0 เพราะ i เป็น 0 ก่อนจะทำ i++ ให้ i กลายเป็น 1
ส่วนข้างซ้ายคือ a[i] และ i กลายเป็น 1 ไปแล้ว
ข้างซ้ายก็จะกลายเป็น a[1]
จับข้างซ้ายมา assign กับข้างขวา ผลคือ
a[1]=0
คือเอาค่า 0 ไปใส่ที่ a[1]

เห็นผลที่แตกต่างกันหรือยังครับ
คงพอนึกออกนะครับว่า source code เดียวกัน
แต่ไป compile และรันในแต่ละ compiler แล้วอาจได้ผลลัพธ์ไม่เหมือนกัน

แล้วเราจะหลีกเลี่ยงปัญหาจาก order of evaluation ได้ยังไง
ถ้าดูจากตัวอย่างข้างบน
statement ที่เป็นปัญหาคือ
printf("%d,%d\n",++i,++i);
กับ
a[i]=i++;
จะมีลักษณะที่เหมือนกันอย่างหนึ่ง คือ
ภายใน statement เดียวกัน
มีการ ใช้ค่า และ เปลี่ยนค่า ของ variable ตัวเดียวกัน มากกว่าหนึ่งครั้ง
ทำให้เกิดทางเลือก ใช้ก่อนเปลี่ยน หรือ เปลี่ยนก่อนใช้
หรือเปลี่ยนค่าหลายครั้ง เลยมีทางเลือกว่าจะเอาค่าไหนมาใช้
เปิดช่องทางเลือกหลายทางให้ compiler นั่นเอง

ทางแก้คืออย่าปล่อยให้มีทางเลือกครับ
ให้แตก statement ออกเป็นหลาย statement แทน
statement ที่ใช้ค่าของ variable ไหนก็จะไม่มีการเปลี่ยนค่าของ variable นั้น
หรือ statement ที่มีการเปลี่ยนค่าของ variable ก็จะไม่ใช้ค่าของ variable นั้น
เช่น
printf("%d,%d\n",++i,++i);
เปลี่ยนเป็น
printf("%d,%d\n",i+1,i+2); //ไม่มีการเปลี่ยนค่า i
i+=2; // เปลี่ยนค่า i
และ
a[i]=i++;
เปลี่ยนเป็น
a[i]=i; //ไม่มีการเปลี่ยนค่า i
i++; // เปลี่ยนค่า i
ก็จะไม่เกิดปัญหาจาก order of evaluation

ประมาณว่าเขียน statement แบบง่ายๆ ไม่ซับซ้อน จะมีปัญหาน้อยกว่าครับ

ตัวอย่างที่ยกมา เป็นแบบที่เห็นปัญหาได้ง่าย การเปลี่ยนค่าของ variable
ทำแบบง่ายๆ เห็นได้ชัดเจนว่าคือ ++i หรือ i++
แต่ในการทำงานจริง การเปลี่ยนค่าของ variable หรือ state จะซับซ้อนกว่านี้มาก
ตัวอย่างเช่น int fgetc(FILE *fp) เป็น function ที่ใช้อ่านค่าหนึ่ง byte จาก file
โดยมี FILE *fp เป็น file pointer
การเรียกใช้ fgetc จะมีการเปลี่ยนค่า variable หรือ state ภายในของ fp ด้วย ดังนั้น
printf ("%d,%d\n",fgetc(fp),fgetc(fp));
ก็จะได้ผลไม่เหมือนกัน เวลาเปลี่ยน compiler หรือ platform
การแก้ก็ต้องทำ statement ให้ย่อยลง ให้เปลี่ยนค่าแค่ครั้งเดียว เช่น
printf("%d,",fgetc(fp));
printf("%d\n",fgetc(fp));

เห็นมั๊ยครับว่า หาที่ผิดได้ยากขนาดไหน

แล้วทำไม standard C ถึงไม่กำหนดไปเลยว่าให้ compiler ทำให้เหมือนกัน
เหตุผลก็คือว่า order of evaluation เป็นเรื่องของ compiler กับ architecture ของ CPU ครับ
เพื่อให้ได้ code ที่มีประสิทธิภาพสูง ทำงานเร็ว เลยต้องปล่อยให้ compiler เป็นผู้เลือกว่าจะทำยังไง

สรุปว่าถ้าอยากให้โปรแกรมทำงานเร็ว ก็ต้องเขียนโปรแกรมยากๆหน่อย
speed does matter จริงๆ





 

Create Date : 27 สิงหาคม 2551    
Last Update : 27 สิงหาคม 2551 21:19:34 น.
Counter : 705 Pageviews.  

Implementation-defined (2)

implementation-defined อีกเรื่องคือ alignment of members of structure ครับ

เวลาที่เรา declare structure ตัวอย่างเช่น
struct x { char a; int b; long c; };

การเรียงตัวของข้อมูลใน memory ของ structure นี้จะเป็น
|a|pad|b|pad|c|pad|

a, b, c คือค่าของ member ทั้งสาม
ส่วน pad คือช่องว่างครับ ซึ่งอาจจะมีหรือไม่มีก็ได้

pad แต่ละตัวอาจมีขนาดไม่เท่ากันก็ได้
pad ที่อยู่หลัง member แต่ละตัวมีขึ้นเพื่อให้ member ตัวถัดไป align อยู่บน memory boundary ที่เหมาะสม
ส่วน pad ตัวสุดท้ายจะ align ขนาดของ structure
ซึ่งทำให้เวลาที่มี array ของ structure จุดเริ่มต้นของ structure แต่ละตัวจะถูก align ไปด้วย

ทำไมต้องมีการทำ alignment ด้วย

ก็เพราะว่าการ access memory ที่อยู่บน boundary ที่เหมาะสมจะทำได้เร็วกว่า
อันนี้ขึ้นกับ platform ครับ
standard C เลยกำหนดให้ compiler เป็นผู้เลือกว่าจะ alignment ยังไง
มาดูตัวอย่างว่า compiler แต่ละตัวทำ alignment ยังไง

จากรูปจะเป็น alignment ของ C compiler ของ sun

จะเห็นได้ว่าบน CPU SPARC (sun) กับ x86 (intel) จะมี alignment ไม่เหมือนกัน

ส่วน Visual C ของ Microsoft สามารถกำหนดใน Project Settings หรือ comand line option ว่าจะให้ทำ alignment แบบไหน




 

Create Date : 23 สิงหาคม 2551    
Last Update : 23 สิงหาคม 2551 18:53:31 น.
Counter : 656 Pageviews.  

Implementation-defined (1)

ใน entry ที่แล้ว ผมยกตัวอย่าง implementation-defined ที่พบบ่อยว่าคือ data type ที่เป็น char
char เป็น data type ที่มีขนาด 8 bits (ตาม standard บอกว่าอย่างน้อย 8 bits
แต่ผมเคยเห็นที่มากกว่า 8 คือ 16 bits แค่ platform เดียว และก็เลิกทำไปแล้ว)
ใน standard ไม่ได้กำหนดว่า char เฉยๆจะเป็น signed char หรือ unsigned char
ให้ compiler ไปกำหนดเอง ทีนี้ลองดู code นี้
char n;
n = -1;
printf("n=%dn",n);
code ข้างบน ถ้าไปรันกับ compiler ที่คิดว่า char เป็น signed char ก็จะได้ผลเป็น
n=-1
แต่ถ้าไปรันกับ compiler ที่คิดว่า char เป็น unsigned char ก็จะได้ผลเป็น
n=255
จะเห็นได้ว่า code เดียวกันแต่ผลไม่เหมือนกัน
ถ้าอยากเขียน code ให้ไม่ขึ้นกับ compiler ก็ให้ระบุไปเลยว่าเป็น signed char หรือ unsigned char เช่น
signed char i;
unsigned char j;
i = -1;
j = -1;
printf("i=%d,j=%dn",i,j);
จะได้ผลเป็น
i=-1,j=255
เสมอไม่ว่าจะใช้ compiler ตัวไหน

เรื่อง char นี่จะเกี่ยวกับภาษาไทยด้วย เพราะ code ภาษาไทยไม่ว่าจะเป็นมาตรฐาน TIS-620
หรือมาตรฐาน microsoft Windows-874 ตัวอักษรไทยตัวแรกคือ กอไก่ จะมีค่าเป็น 161 (0xA1)
และตัวอักษรที่เหลือก็จะมีค่าไล่ตามกันไป ดูรูปจาก link ได้เลยครับ
//www.inet.co.th/cyberclub/trin/thairef/tis-620.gif
ซึ่งถ้า char เป็น signed char ค่าที่มากกว่า 127 (0x7F) จนถึงค่า 255 (0xFF) จะกลายเป็นค่าลบ
ดังนั้นตัวอักษรไทยทั้งหมดจะมีค่าติดลบ ทำให้โปรแกรมทำงานผิดพลาดได้
เราจึงควรใช้ unsigned char สำหรับ code ที่รองรับภาษาไทยครับ

ยังมีต่อนะครับ




 

Create Date : 20 สิงหาคม 2551    
Last Update : 20 สิงหาคม 2551 17:37:35 น.
Counter : 657 Pageviews.  

ระวังถูก compiler หลอก (2)

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

ต่อมาพอผมเริ่มเขียนภาษา C ก็เห็นว่ามีเรื่องยากอยู่ไม่กี่เรื่องที่อาจทำให้เกิดข้อผิดพลาดได้
เช่นเรื่องของการทำ indent ไม่ถูกที่ ทำให้งงกับ logic ของ if-else
หรือเรื่อง pointer ที่เวลาเขียนผิดแล้วอาจทำให้โปรแกรมตายได้
มันก็เป็นเรื่องปรกติ ก็คนเขียนมันเขียนผิดนี่

จนอีกหลายปีถัดมาผมได้มีโอกาสไปเขียนภาษา C บนเครื่อง server
แล้วพบว่าภาษา C ที่ผมเคยเขียนได้ กลับ compile ไม่ผ่าน หรือรันแล้วเพี้ยนๆ หรือมี error
ผมเลยกลับมาอ่าน textbook ภาษา C ใหม่ คราวนี้อ่านอย่างละเอียด
ที่ผ่านมาถึงแม้ว่าผมเขียนโปรแกรมภาษา C มาหลายปี
มีโปรแกรมที่ทำงานได้ออกมามากมาย แต่ผมก็ไม่ได้เข้าใจภาษา C จริงๆ
การอ่าน textbook ทำให้ผมเข้าใจดีขึ้น และเห็นว่า ภาษา C เกิดข้อผิดพลาดได้ง่ายจริงๆ

ตอนที่ผมเริ่มหัดเขียนภาษา C ผมก็เริ่มแบบที่คนทั่วๆไปทำกัน
คือหาหนังสือมาเล่มนึงกับ compiler ตัวนึง แล้วก็เริ่มหัดเขียน
พอเริ่มเขียนได้บ้างก็เลิกอ่านหนังสือ แล้วใช้ compiler เป็นครูแทน
สงสัยอะไรก็เขียนโปรแกรมทดสอบ แล้ว compile และรันดูว่าใช้ได้หรือเปล่า
ผลก็คือผมเขียนภาษา C ที่ถูกต้องตาม compiler กับ platform ที่ผมใช้
ไม่ใช่เขียนภาษา C ที่ถูกต้องตาม standard ของภาษา
เรียกได้ว่าถูก compiler หลอกเอาครับ
พอเปลี่ยน compiler หรือ platform ก็เลยเกิดปัญหามากมาย
ผมเข้าใจว่านี่เป็นปัญหาที่เกิดขึ้นกับคนส่วนใหญ่ด้วย
โดยเฉพาะเดี๋ยวนี้ compiler หลายตัว เป็น C/C++ คือใช้ได้ทั้งภาษา C กับ C++
เลยไม่รู้ว่าเป็นภาษาไหนกันแน่ ลองอ่านที่ผมเขียนเล่าไว้ใน entry ที่แล้วดู

ข้อผิดพลาดที่พบได้บ่อยของภาษา C มีต้นเหตุมาจาก 3 เรื่องหลักๆ คือ
- implementation-defined
- undefined
- syntax rule

implementation-defined หมายความว่า ให้เป็นเรื่องของ compiler แต่ละตัวไปทำเอง
ใน standard ไม่ได้กำหนดว่าต้องทำยังไง
ซึ่ง compiler แต่ละตัวต้องกำหนด (define) ให้ชัดเจนไปเลยว่าจะทำ (implement) ยังไง
ตัวอย่างของ implementation-defined ที่พบบ่อยคือ data type ที่เป็น char
ใน standard ไม่ได้กำหนดว่า char จะเป็น signed char หรือ unsigned char
ให้ compiler แต่ละตัวไปกำหนดเอาเอง

ส่วน undefined หมายความว่า ใน standard ไม่ได้กำหนดว่าจะทำยังไง หรือเกิดอะไรขึ้น
compiler ก็ไม่จำเป็นต้องกำหนดว่าจะทำยังไง หรือเกิดอะไรขึ้น สรุปว่ามีอิสระกันได้เต็มที่
ตัวอย่างของ undefined คือ ค่าเริ่มต้นของ automatic variable ซึ่งจะเป็นค่าอะไรก็ได้

สองข้อนี้แหละครับที่ทำให้เขียนโปรแกรมถูก แต่ไป compile และรันในแต่ละ compiler แล้วอาจได้ผลลัพธ์ไม่เหมือนกัน
ส่วน syntax rule ก็เป็นปัญหาที่เกิดจาก syntax ของภาษา C เอง
ที่ออกแบบมาให้ยืดหยุ่นมาก แต่เปิดช่องให้ทำผิดได้ง่ายๆ

คราวนี้พอแค่นี้ก่อนนะครับ โอกาสหน้าจะเอาตัวอย่างข้อผิดพลาดที่เกิดขึ้นในแต่ละเรื่องมาให้ดูครับ




 

Create Date : 18 สิงหาคม 2551    
Last Update : 18 สิงหาคม 2551 19:19:07 น.
Counter : 569 Pageviews.  

ระวังถูก compiler หลอก (1)

เคยเห็นกระทู้ถามปัญหาภาษา C ในพันทิปกระทู้นึง เจ้าของกระทู้เอา source code ที่สงสัยมาให้ดู
ผมเห็นว่า source code ที่ให้มา ไม่น่าจะ compile ผ่าน แต่คำถามที่เจ้าของกระทู้ถาม แสดงว่าได้ทดลอง compile มาแล้ว
code ที่ผมคิดว่าไม่น่าจะ compile ผ่านคือ
srand(unsigned(time(NULL)));
ตรง unsigned(time(NULL)) เข้าใจว่าเป็นการ cast ค่าที่ return มาจาก function time ให้เป็น unsigned
แต่ syntax การ cast ในภาษา C ต้องใส่วงเล็บด้วย ดังนั้นตรง unsigned ต้องเป็น (unsigned)
code ที่ถูก ต้องเป็นแบบนี้ครับ
srand((unsigned)(time(NULL)));

ด้วยความสงสัยก็เลยลองเอา code ที่ผิด ไป compile ด้วย gcc บน linux ผลลัพธ์เป็นไปตามที่คาด คือ compile ไม่ผ่านครับ
แต่นึกได้ว่ามี compiler อีกตัวชื่อ Dev-C ที่พึ่ง download มา เลยลองเอามา compile ดู คราวนี้ปรากฎว่า compile ผ่าน
ทำเอาผมงงไปเลยครับ ความคิดแรกที่แว่บเข้ามาคือผมต้องทำอะไรผิด หรือเข้าใจอะไรผิด แน่ๆ
ตอน download Dev-C จาก web site ก็เห็นว่าใช้ compiler ของ gnu ไม่น่าจะมีปัญหาเรื่อง standard
เลยหาดูว่า Dev-C ใช้ compiler ตัวไหน ไปเห็นว่าใช้ g++ อยู่
ซึ่ง g++ เป็น compiler สำหรับภาษา C++ ไม่ใช่ compiler สำหรับภาษา C
ผมลองแก้ให้ใช้ gcc เป็น compiler ผลปรากฏว่า compile ไม่ผ่าน

แสดงว่าถ้าเอา source code ภาษา C ไป compile ด้วย compiler สำหรับภาษา C++
อาจได้ผลลัพธ์ผิดๆ เพราะถูก compiler หลอกเอาได้

ยังมีตัวอย่างที่ถูก compiler หลอกอีกหลายตัวอย่าง เอาไว้มีเวลาจะมาเล่าให้ฟังอีกครับ






 

Create Date : 13 สิงหาคม 2551    
Last Update : 13 สิงหาคม 2551 21:05:31 น.
Counter : 977 Pageviews.  

1  2  

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.