Dereferencing (*): ทะลวงเข้าถึงหน่วยความจำ แก่นแท้ของการสั่งงานฮาร์ดแวร์ด้วย Pointer
Dereferencing (*): ทะลวงเข้าถึงหน่วยความจำ แก่นแท้ของการสั่งงานฮาร์ดแวร์ด้วย Pointer
สวัสดีครับน้องๆ วิศวกรและเพื่อนนักพัฒนาชาว www.123microcontroller.com ทุกคน! กลับมาพบกับวิศวกรขอบตาดำๆ กันอีกครั้งครับ
ในบทความก่อนหน้านี้ เราได้รู้จักกับเครื่องหมาย & (Address-of Operator) ที่ใช้สำหรับ “ขอพิกัดที่อยู่” ของตัวแปรกันไปแล้ว แต่น้องๆ ลองจินตนาการดูสิครับว่า ถ้าเรามีพิกัดที่อยู่ของเซ็นเซอร์อุณหภูมิ หรือที่อยู่ของ Register ควบคุมมอเตอร์อยู่ในมือ แต่เรา “ไม่สามารถเดินทางไปอ่านค่าหรือสั่งงานมันได้” พิกัดเหล่านั้นก็แทบจะไร้ประโยชน์เลยใช่ไหมครับ?
ในโลกของภาษา C กุญแจสำคัญที่จะช่วยให้เรา “เดินทางตามพิกัด” เพื่อเข้าไปจัดการข้อมูลที่ปลายทางได้ก็คือ การเข้าถึงค่าในตำแหน่ง (Dereferencing) ด้วยเครื่องหมายดอกจัน (*) ครับ! วันนี้เราจะมาเจาะลึกจากแหล่งข้อมูลระดับโลกกันว่า เครื่องหมาย * ทำงานอย่างไร มันเกี่ยวข้องกับชนิดข้อมูล (Data Type) อย่างไร และทำไมมันถึงเป็นหัวใจสำคัญของการเขียนโปรแกรมระดับ System Programming ไปลุยกันเลยครับ!
กลไกการทะลวงหน่วยความจำ (Indirection/Dereferencing)
ในบริบทของพื้นฐานและส่วนประกอบของ Pointer แหล่งข้อมูลชั้นครูได้อธิบายกระบวนการ Dereferencing (หรืออีกชื่อหนึ่งคือ Indirection) เอาไว้ดังนี้ครับ:
1. การเดินตามพิกัด (Following the Pointer)
กระบวนการตามรอย Pointer ไปยังตำแหน่งหน่วยความจำที่มันชี้อยู่เรียกว่า Indirection หรือ การ Dereference Pointer ตัวดำเนินการที่ใช้ทำหน้าที่นี้คือเครื่องหมาย * (Unary * operator) หรือที่บางครั้งเรียกว่า “Value at” operator เปรียบเทียบง่ายๆ เหมือนเราดูแผนที่ (Address) ขับรถไปถึงหน้าบ้าน จากนั้นก็เปิดประตูเข้าไปดูว่าในบ้านมีข้อมูลอะไรอยู่นั่นเองครับ
2. สองบทบาทที่แตกต่าง (R-value และ L-value)
เมื่อเราใส่เครื่องหมาย * หน้า Pointer มันสามารถทำหน้าที่ได้ 2 รูปแบบ ขึ้นอยู่กับว่ามันอยู่ฝั่งไหนของสมการ:
- เมื่อใช้อ่านค่า (R-value): หากอยู่ฝั่งขวาของสมการ (เช่น
y = *ptr;) มันจะทำหน้าที่เดินทางไปที่หน่วยความจำนั้น แล้ว “ดึงค่า (Value)” ที่ถูกเก็บอยู่ออกมาใช้งาน - เมื่อใช้เขียนค่า (L-value): หากอยู่ฝั่งซ้ายของสมการ (เช่น
*ptr = 200;) มันจะทำหน้าที่ “ระบุตำแหน่ง (Location)” ที่เจาะจงในหน่วยความจำ เพื่อเปิดทางให้เรานำค่าใหม่ (200) ไปเขียนทับลงในตำแหน่งปลายทางนั้น
3. ชนิดข้อมูลบอกวิธีการตีความ (Type Interpretation)
นี่คือจุดที่สำคัญมากสำหรับสายฮาร์ดแวร์! เมื่อเราสั่ง Dereference คอมไพเลอร์จะรู้ได้อย่างไรว่าต้องอ่านข้อมูลกี่ไบต์? คำตอบคือมันดูจาก “ชนิดของ Pointer (Data Type)” ครับ
หาก Pointer เป็นชนิด int32_t * (32-bit) เวลาเราสั่ง *ptr คอมไพเลอร์จะกวาดข้อมูลมา 4 ไบต์และตีความเป็นตัวเลขจำนวนเต็ม แต่ถ้าเป็น char * มันจะกวาดมาแค่ 1 ไบต์ ชนิดของ Pointer จึงเป็นตัวกำหนดพฤติกรรมของการ Dereference อย่างสมบูรณ์แบบ
4. การเข้าถึงหลายระดับ (Multiple Indirection)
หากเรามี Pointer ที่ชี้ไปยัง Pointer อีกที (เช่น int **ppi;) เราสามารถใช้เครื่องหมาย * ซ้อนกันได้ครับ การทำ **ppi หมายถึง: การอ่านค่า Pointer ตัวแรกเพื่อหา Address ถัดไป -> แล้วกระโดดไปตาม Address นั้น -> เพื่อเข้าถึงค่าข้อมูลจริงๆ ที่อยู่ปลายทางสุด

ตัวอย่างโค้ด (Code Example)
มาดูตัวอย่างการใช้ * สำหรับการอ่านและเขียนค่า (R-value & L-value) รวมถึงการตีความตามชนิดข้อมูล สไตล์ Clean Code กันครับ:
#include <stdio.h>
#include <stdint.h>
int main(void) {
/* จำลองตัวแปรฮาร์ดแวร์ที่เก็บข้อมูลขนาด 4 ไบต์ (32-bit) */
uint32_t hardware_register = 0x11223344;
/* 1. สร้าง Pointer ชี้ไปยัง hardware_register */
uint32_t *ptr_reg = &hardware_register;
/*
* 2. การใช้ Dereference เป็น R-value (เพื่ออ่านค่า)
* ดึงค่าจากตำแหน่งที่ ptr_reg ชี้อยู่ (0x11223344) ออกมาแสดงผล
*/
printf("Current Register Value: 0x%X\n", *ptr_reg);
/*
* 3. การใช้ Dereference เป็น L-value (เพื่อเขียนค่า)
* สั่งเขียนค่า 0xFFFFFFFF ทับลงไปที่ตำแหน่งที่ ptr_reg ชี้อยู่ปลายทาง
*/
*ptr_reg = 0xFFFFFFFF;
printf("New Register Value: 0x%X\n", hardware_register);
/* =========================================================
* 4. ตัวอย่างความสำคัญของ Data Type ตอน Dereference
* ========================================================= */
/* ใช้ byte pointer (อ่านทีละ 1 ไบต์) ชี้ไปที่ที่อยู่เดียวกัน */
uint8_t *byte_ptr = (uint8_t *)&hardware_register;
/* เมื่อทำ Dereference ที่ byte_ptr มันจะถูกบังคับให้อ่านมาแค่ 1 ไบต์ (0xFF) */
printf("Reading just 1 byte: 0x%X\n", *byte_ptr);
/* =========================================================
* 5. การใช้ Multiple Indirection (Pointer to Pointer)
* ========================================================= */
uint32_t **ptr_to_ptr = &ptr_reg;
/* สั่ง Dereference 2 ครั้งเพื่อเปลี่ยนค่าตัวแปรดั้งเดิมที่อยู่ลึกสุด */
**ptr_to_ptr = 0x00000000;
printf("Register Value after **: 0x%X\n", hardware_register);
return 0;
}
ข้อควรระวัง / Best Practices
การ Dereference Pointer เปรียบเสมือนการถือปืนเลเซอร์ หากเล็งผิดเป้าหมาย ระบบพังพินาศทันที! ตำรา Expert C และ Secure Coding เน้นย้ำข้อควรระวังไว้ดังนี้ครับ:
- ห้าม Dereference
NULLPointer เด็ดขาด: ตามนิยามแล้วNULLPointer คือ Pointer ที่ “ไม่ได้ชี้ไปที่ไหนเลย” การพยายามใส่*หน้าตัวแปรที่มีค่าNULLจะทำให้โปรแกรมทำงานผิดพลาด (Undefined behavior) หรือเกิดอาการจอฟ้า/โปรแกรมดับ (Segmentation fault) ทันที กฎเหล็กคือ ต้องตรวจสอบif (ptr != NULL)ก่อนทำการ Dereference เสมอ - มหันตภัยจาก Pointer ที่ยังไม่ได้เริ่มต้น (Uninitialized Pointers): เมื่อคุณประกาศ Pointer ขึ้นมา (เช่น
int *pi;) มันจะถือค่าขยะในหน่วยความจำ หากคุณสั่ง*pi = 10;ทันที นั่นคือการเอาค่า 10 ไปเขียนทับหน่วยความจำแบบสุ่ม ซึ่งอาจไปทับตัวแปรอื่น หรือทำให้ OS สั่งปิดโปรแกรมคุณทันทีเพราะไปล่วงล้ำพื้นที่สงวน - ระวังการแปลงชนิด Pointer (Casting) และนำไป Dereference: หากคุณมีพื้นที่หน่วยความจำขนาดเล็ก (เช่น ตัวแปร
char1 ไบต์) แล้วคุณแคสต์เป็นint32_t *แล้วสั่ง Dereference เพื่อเขียนข้อมูล (เช่น*int_ptr = 1000;) ข้อมูล 4 ไบต์จะทะลักขอบเขตของตัวแปร (Buffer Overflow) ไปทับข้อมูลชาวบ้านข้างเคียงทันที นี่คือช่องโหว่ด้านความปลอดภัยที่ร้ายแรงมาก
สรุปทิ้งท้าย
การเข้าถึงค่าในตำแหน่ง (Dereferencing) ผ่านเครื่องหมาย * คือกลไกที่ทำให้ภาษา C สามารถจัดการกับหน่วยความจำและฮาร์ดแวร์ได้อย่างแท้จริงครับ มันเป็นได้ทั้งเครื่องมือในการ “อ่าน” (R-value) และ “เขียน” (L-value) โดยมีชนิดของ Pointer คอยกำกับพฤติกรรมอยู่เบื้องหลัง การเข้าใจกลไกนี้อย่างถ่องแท้จะช่วยให้น้องๆ เขียนโค้ดควบคุมไมโครคอนโทรลเลอร์ได้อย่างแม่นยำและปลอดภัยครับ
น้องๆ คนไหนเคยเจอบั๊กจอฟ้าจากการเผลอไป Dereference ใส่ NULL หรือเคยสับสนปวดหัวกับเครื่องหมาย ** ซ้อนกันหลายๆ ตัว อย่าเก็บความปวดหัวไว้คนเดียวนะครับ! แวะเข้ามาตั้งกระทู้แปะโค้ด และพูดคุยกันต่อได้ที่บอร์ด www.123microcontroller.com ของพวกเราได้เลย แล้วพบกันใหม่ในบทความหน้า Happy Coding ครับทุกคน!