ความรู้เกี่ยวกับการเขียนโปรแกรมภาษา C ที่นักอิเล็กทรอนิกส์ไม่ค่อยรู้ ตอนที่ 5 การใช้ Pointer กับ Function

ความรู้เกี่ยวกับการเขียนโปรแกรมภาษา C ที่นักอิเล็กทรอนิกส์ไม่ค่อยรู้

ตอนที่ 5 การใช้ Pointer กับ Function

The C Programming Language logo

         นอกจากเราจะใช้ตัวแปร Pointer เพื่อชี้ไปยังตัวแปรตัวอื่นๆแล้ว เรายังสามารถใช้ Pointer กับ Function ได้ด้วย ทำให้เรามีตัวชี้สำหรับ function ที่สามารถจะชี้ไปที่ function ที่ 1,2 หรือ function ใดๆ ก็ได้ ตามที่ถูกกำหนดตำแหน่งให้ชี้ ถ้านึกไม่ออกว่าจะใช้ตอนไหน ลองมาดูตัวอย่างต่อไปนี้
         สมมติว่าเราเขียนโปรแกรมหรือ library ในการรับส่งข้อมูลทาง UART เป็น protocol สักอย่างหนึ่ง โดยเป็นการเขียนเพื่อให้คนอื่นเอาไปใช้ ส่วนที่เป็น function ในการส่งข้อมูล จะเป็น function ที่จะถูกเรียกใช้เพียงอย่างเดียวจึงใช้ function ธรรมดาก็ได้ แต่ในส่วนที่เป็นการรับข้อมูล เมื่อรับข้อมูลเป็น protocol มาจนครบแล้ว เราจะต้องเรียก function ที่อยู่ข้างนอก library เพื่อให้นำข้อมูลไปใช้ โดยที่ไม่ให้คนอื่นมาเขียน code ในไฟล์ของเรา (ภาษาอังกฤษเรียกว่าเป็นการ call back) ในกรณีนี้ เราสามารถจะสร้าง pointer เป็นตัวชี้ไปยัง function เอาไว้ เช่น
void(*callback_func)(unsigned char *dat, int len); //เป็นการสร้าง pointer สำหรับชี้ไปยัง function ใดๆ
ที่เป็น void (xxx)(unsigned char *dat, int len) คือต้องมีการรับ input และมีการ return ที่เหมือนกัน โดยในที่นี้ *dat เป็นตำแหน่งของข้อมูล และ len เป็นความยาวของข้อมูล

         และเราอาจทำการประกาศ extern pointer ของเราไว้ในไฟล์ header เพื่อให้ผู้ใช้สามารถกำหนดให้ pointer ชี้ไปยัง function ที่สร้างขึ้นมาได้ โดยผู้ที่นำ library ไปใช้จะต้อง include ไฟล์ header ของเราเพื่อให้รู้จักกับตัวแปร pointer callback_func (อย่าลืมว่านี่คือตัวแปร pointer ไม่ใช่ function นะครับ)
extern void(*callback_func) (unsigned char *dat, int len); //ประกาศไว้ในไฟล์ header ของเรา เพื่อใช้ในการ include ในไฟล์ที่นำ library ไปใช้

         จากนั้นในการกำหนดให้ pointer callback_func ชี้ไปยัง function ที่ต้องการ จะสามารถเขียนได้ดังนี้
callback_func = xxx; //ชื่อ function ที่ต้องการให้ชี้ไป โดย function ที่จะชี้ไปได้ ต้องมีการรับ input และมีการ return ที่เหมือนกันดังที่ได้กล่าวมาแล้ว

         ผู้ที่ใช้ library ของเรา จะต้องสร้าง function ขึ้นมา โดยจะตั้งชื่อว่าอะไรก็ได้ แล้วมากำหนดให้ตัวชี้ของเรา ชี้ไปยัง function นั้น เช่น
void process_rx_data(unsigned char *dat, int len); //ตัวอย่าง function ที่ผู้ใช้สร้างขึ้นมา
callback_func = process_rx_data; //กำหนดให้ pointer ชี้ไปยัง function ที่สร้างขึ้น โดยเวลาที่เราเขียนชื่อ function เฉยๆ ไม่มี () นั่นหมายถึงการนำเอาตำแหน่งของ function นั้นมาใช้งาน

         จากนั้นเวลาเราเขียนโปรแกรมใน library ของเราเพื่อให้ function ที่ถูกชี้ ทำงานเราสามารถเขียนได้ดังนี้
callback_func(rx_data, rx_data_len); //ให้ function ที่ถูกชี้อยู่ทำงาน โดยตัวแปร rx_data (เป็น array) และ rx_data_len เป็นตัวแปรที่เราเป็นคนสร้างขึ้นมาและใส่ข้อมูลเอาไว้ก่อนแล้ว การที่เราส่งค่าเข้าไปใน function ก็จะเป็นการส่งค่าไปให้ function ที่ถูกชี้อยู่นำไปใช้

         นอกจากนี้ถ้าเราจะไม่ประกาศ extern pointer ของเรา อาจจะทำอีกวิธีหนึ่งก็ได้ คือการสร้าง function ขึ้นมาเพื่อรับค่า pointer สำหรับชี้ไปยัง function อีกทีหนึ่ง เช่น?

void bind_callback_func(void(*func)(unsigned char *dat, int len)) {
    callback_func = func;
}

โดยในการใช้งาน ก็ส่งตำแหน่งของ function process_rx_data เข้าไปดังนี้

bind_callback_func (process_rx_data);

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

void(*callback_func)(unsigned char *dat, int len); //pointer สำหรับชี้ไปยัง function
unsigned char rx_data[10]; //สมมติว่าข้อมูลมีขนาดไม่เกิน 10 byte 
int rx_data_len; //เก็บความยาวของข้อมูล
void receive_rx(void) {
    unsigned char rx; //ตัวแปรสำหรับรับข้อมูลแต่ละ byte
    while(rx_data_valid()) { //วน loop เพื่ออ่านข้อมูลที่เข้ามาทั้งหมด
        rx=UART_RX; //อ่านข้อมูลที่รับเข้ามาทีละ byte
        switch (rx_state) { //ประมวลผลข้อมูลว่าอยู่สถานะไหน เช่น สถานะรอข้อมูล (idle), สถานะ  command, data หรือ checksum
            … 
            callback_func(rx_data,rx_data_len); //เมื่อ checksum และข้อมูลทุกอย่างถูกต้อง จึงส่งข้อมูลไปยัง callback_func
        }
    }
}

         ในตัวอย่างที่ผ่านมา เราจะได้เห็นประโยชน์ของการใช้ pointer ชี้ไปยัง function แล้ว ด้วยวิธีการเดียวกันนี้ เราสามารถนำไปประยุกต์ใช้งานในกรณีอื่นๆอีก โดยมักจะใช้กับการสร้าง library หรือ OS ที่มีโครงสร้างของโปรแกรมที่ยืดหยุ่น สามารถนำไปปรับเปลี่ยนการใช้งานได้โดยที่ไม่ต้องมาแก้ไขที่โปรแกรมหลัก ยกตัวอย่างเช่น ในสินค้าของเราคือบอร์ด mini SUN7 ที่เป็นบอร์ดควบคุมจอ LCD พร้อมกับ touch screen เราได้สร้าง library สำหรับรองรับการออกแบบหน้าจอให้มี component ต่างๆเช่น ปุ่มกด ตาราง กล่องข้อความ ฯลฯ action ที่เราต้องการให้เกิดขึ้นจากการกระทำกับ component เหล่านั้น ไม่สามารถจะเขียนเป็น function ที่ตายตัวได้ เพราะต้องขึ้นอยู่กับผู้ใช้ที่ออกแบบหน้าจอ ดังนั้นเราจึงต้องสร้าง pointer และให้ผู้ใช้สร้าง function ของตัวเองขึ้นมา แล้วจึงให้ชี้ pointer ไปยัง function ของตัวเอง

ข้อควรระวังในการใช้งาน
การใช้งาน pointer ไม่ว่าจะเป็น pointer ชี้ไปยังตัวแปร หรือชี้ไปยัง function ในตอนที่เราประกาศ pointer ขึ้นมา pointer จะยังไม่ได้ถูกกำหนดว่าจะให้ชี้ไปที่ใด ตำแหน่งเริ่มต้นตั้งแต่ตอนประกาศนี้ จะขึ้นอยู่กับ compiler หรือไม่ก็เป็นค่าใน memory ณ ตำแหน่งนั้น และ ณ ขณะนั้น ว่ามีค่าเป็นเท่าใดอยู่ โดยทั่วไปจะถูกกำหนดให้มีค่าเป็น 0x00000000 ซึ่งตำแหน่งดังกล่าวมักจะเป็นส่วนที่ใช้เก็บ code ของ MCU (เป็นตำแหน่งที่เริ่มจากการ reset)
         ทีนี้สมมติว่าถ้า pointer ชี้ไปที่ตำแหน่ง 0x00000000 แล้วจะเกิดอะไรขึ้นหากถูกนำไปใช้งาน ถ้าเป็น pointer ที่ชี้ไปยังตัวแปร การอ่านค่าจากตำแหน่งนั้นก็จะเป็นการอ่านค่าของ code ที่ตำแหน่งนั้นออกมา ก็อาจจะไม่เป็นอะไร แต่ถ้าหากเป็นการเขียนค่าลงไป (เขียนค่าไปที่ตำแหน่งที่ pointer ชี้) ก็จะเกิด error ขึ้น เพราะเป็นตำแหน่งที่ไม่สามารถจะเขียนค่าลงไปจากโปรแกรมได้ (จริงๆแล้วเขียนได้ แต่ต้องมีขั้นตอนที่ถูกต้อง) ถ้าเป็น ARM จะเกิด Hard Fault Error (สามารถดูได้จากการ debug) แต่ภายนอกคือที่ hardware เราจะเห็นว่า MCU ค้าง 
ส่วนถ้าเป็น pointer ที่ชี้ไปยัง function ถ้าเป็นการกระโดดไปทำที่ตำแหน่ง 0x00000000 นี้จะเป็นการ reset โดยที่เราไม่ได้ตั้งใจ ดังนั้นในการใช้งาน pointer ในลักษณะนี้จึงมักจะต้องกำหนด function เปล่าๆขึ้นมาไว้รองรับก่อน จากตัวอย่างอาจจะสร้าง function ในบทความตอนนี้ เรา เอาไว้ดังนี้?

void callback_func_null (unsigned char *dat, int len) {
} //ไม่ได้ทำอะไร เพียงแค่สร้าง function เปล่าๆเอาไว้เป็นตำแหน่งเริ่มต้น

         และทำการกำหนดให้ pointer ชี้ไปยัง function นี้ตั้งแต่เริ่มต้นดังนี้

callback_func = callback_func_null;

         จากบทความเรื่อง pointer ทั้ง 3 ตอน จะเห็นว่า pointer มีประโยชน์ในการเขียนโปรแกรมอย่างมาก จึงเป็นสิ่งจำเป็นสำหรับการเขียนโปรแกรมให้มีความยืดหยุ่นและมีประสิทธิภาพ แต่ก็ต้องถูกนำไปใช้อย่างระมัดระวัง เพราะการใช้ pointer โดยไม่ระวังจะทำให้เกิด bug ได้ง่าย สำหรับเรื่อง pointer ก็คงมีเท่านี้ แต่จะไปแทรกอยู่ในเรื่องอื่นๆอีกในบทความตอนต่อๆไป ส่วนจะเป็นเรื่องอะไรนั้นต้องฝากติดตามด้วยครับ

<< กลับไปสู่หน้าแรกสารบัญ