Ch8: Pointers¶
TL;DR: Memory Addressing: Pointer คือตัวแปรที่ใช้เก็บที่อยู่ในหน่วยความจำ (Memory Address) ของตัวแปรอื่น มีขนาด 8 Byte ในระบบ 64-bit และเรียกดูหรือแก้ไขค่าผ่านการ De-reference (
*)
⚡ Quick Reference¶
#include <iostream>
int main() {
int value = 42;
// type* ptr - ประกาศพอยน์เตอร์ชี้หาตำแหน่งประเภทข้อมูล
int* p_val = nullptr; // nullptr - พอยน์เตอร์ว่างเปล่าที่ปลอดภัย
// & - ตัวดำเนินการนำแอดเดรส (Address-of operator)
p_val = &value;
// * - ตัวดำเนินการถอดค่าอ้างอิง (Dereference operator) เพื่ออ่านเขียนค่าในแอดเดรส
int copied_val = *p_val;
// const type* - พอยน์เตอร์ชี้หาข้อมูลคงที่ (เปลี่ยนแอดเดรสได้ แต่แก้ไขข้อมูลไม่ได้)
const int* p_const_val = &value;
// type* const - พอยน์เตอร์ชนิดคงที่ (ห้ามเปลี่ยนแอดเดรส แต่แก้ไขข้อมูลปลายทางได้)
int* const const_p_val = &value;
// const type* const - พอยน์เตอร์ชนิดคงที่ชี้หาข้อมูลคงที่ (ห้ามเปลี่ยนค่าใดๆ เลย)
const int* const const_p_const_val = &value;
// new - จองหน่วยความจำระดับออบเจกต์เดี่ยวบน Heap
int* p_heap = new int(100);
// delete - คืนหน่วยความจำระดับออบเจกต์เดี่ยวบน Heap
delete p_heap;
// new[] - จองหน่วยความจำระดับอาร์เรย์บน Heap
int* p_heap_array = new int[5];
// delete[] - คืนหน่วยความจำระดับอาร์เรย์บน Heap
delete[] p_heap_array;
return 0;
}
🧠 Core Concepts¶
- Memory Addressing: Pointer คือตัวแปรที่ใช้เก็บที่อยู่ในหน่วยความจำ (Memory Address) ของตัวแปรอื่น มีขนาด 8 Byte ในระบบ 64-bit และเรียกดูหรือแก้ไขค่าผ่านการ De-reference (
*) - Dynamic Allocation: การจัดสรรหน่วยความจำบน Heap ด้วยโอเปอเรเตอร์
newจะคงอยู่ตลอดไปจนกว่าจะส่งคืนพื้นที่ด้วยคำสั่งdelete(หรือdelete[]สำหรับ Array) เพื่อหลีกเลี่ยงปัญหาทรัพยากรหมดเกลี้ยง - Pointer Decay & Safety: ชื่อของ Array จะลดรูปเป็น Pointer (Pointer Decay) ชี้ไปยังสมาชิกตัวแรกโดยอัตโนมัติ ซึ่งการใช้ Pointer ที่ไม่ได้กำหนดค่าเริ่มต้นหรือ Pointer ที่หมดอายุจะทำให้เกิด Undefined behavior
⚠️ Pitfalls (Quick Scan)¶
| ข้อผิดพลาด | วิธีแก้ |
|---|---|
การประกาศ Pointer หลายตัวในบรรทัดเดียวกัน เช่น int p, var; |
หลีกเลี่ยงการประกาศ Pointer หลายตัวบนบรรทัดเดียวกัน |
การแก้ไขค่าของ String Literal ผ่าน char ที่ไม่ใช่ const |
ใช้ const char* เสมอกับ String Literal หรือใช้ Char Array เพื่อคัดลอกค่าลง Stack |
| พยายามเปลี่ยนที่อยู่หน่วยความจำของชื่อ Array | ใช้ตัวแปร Pointer ธรรมดาหากต้องการเปลี่ยนแปลง Address ในภายหลัง |
พยายามใช้ std::size หรือ Range-based for กับ Dynamic Array |
บันทึกขนาดของ Array ไว้ในตัวแปรแยกต่างหากและใช้ลูปแบบดั้งเดิม |
| พยายามเพิ่มหรือลดค่า (Increment/Decrement) กับชื่อ Array ตรงๆ | สร้างตัวแปร Pointer มารับแอดเดรสก่อนทำคำสั่งเพิ่มหรือลดค่า |
| การนำ Pointer ของ Array คนละตัวมาลบหรือเปรียบเทียบกัน | ลบหรือเปรียบเทียบเฉพาะ Pointer ที่ชี้ไปยังสมาชิกภายใน Array ตัวเดียวกันเท่านั้น |
| การ Dereference ตัวแปร Pointer ที่เก็บค่าขยะหรือเป็น Null Pointer | กำหนดค่าเริ่มต้น Pointer เป็น nullptr เสมอ และตรวจสอบเงื่อนไขก่อนเรียกใช้งาน |
| ลืมคืนหน่วยความจำที่จองไว้บน Heap (Memory Leak) | เรียกใช้คำสั่ง delete หรือ delete[] ทุกครั้งเมื่อใช้งานหน่วยความจำบน Heap เสร็จสิ้น |
| เรียกคืนหน่วยความจำตำแหน่งเดิมซ้ำกันหลายครั้ง (Double-free) | เซ็ตค่า Pointer ให้เป็น nullptr ทันทีหลังเรียกใช้คำสั่ง Delete |
ใช้คำสั่ง delete แบบธรรมดาแทน delete[] ในการเคลียร์ Dynamic Array |
ใช้ delete[] เสมอกับหน่วยความจำที่ถูกจองผ่านคำสั่ง new[] |
📖 Full Details¶
Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู)
* **การประกาศ Pointer หลายตัวในบรรทัดเดียวกัน เช่น `int* p, var;`** -> **มีเพียงตัวแปรแรกที่เป็น Pointer ส่วนตัวแปรที่สองจะเป็นจำนวนเต็มปกติ** -> **หลีกเลี่ยงการประกาศ Pointer หลายตัวบนบรรทัดเดียวกัน (10:00)** * **การแก้ไขค่าของ String Literal ผ่าน `char*` ที่ไม่ใช่ `const`** -> **เกิด Undefined behavior หรือแครชตอน Runtime เนื่องจาก String Literal เก็บในหน่วยความจำแบบ Read-only (27:00)** -> **ใช้ `const char*` เสมอกับ String Literal หรือใช้ Char Array เพื่อคัดลอกค่าลง Stack (27:00)** * **พยายามเปลี่ยนที่อยู่หน่วยความจำของชื่อ Array** -> **เกิด Compile-time error เนื่องจากชื่อ Array เป็น Pointer ชนิดพิเศษที่แก้ไขค่าไม่ได้** -> **ใช้ตัวแปร Pointer ธรรมดาหากต้องการเปลี่ยนแปลง Address ในภายหลัง (77:00)** * **พยายามใช้ `std::size` หรือ Range-based for กับ Dynamic Array** -> **เกิด Compile-time error เนื่องจาก Array ลดรูปเป็นเพียง Pointer ธรรมดาไปแล้ว** -> **บันทึกขนาดของ Array ไว้ในตัวแปรแยกต่างหากและใช้ลูปแบบดั้งเดิม (81:00)** * **พยายามเพิ่มหรือลดค่า (Increment/Decrement) กับชื่อ Array ตรงๆ** -> **เกิด Compile-time error เพราะชื่อ Array เป็นค่าคงที่ที่ไม่สามารถเปลี่ยนแอดเดรสได้** -> **สร้างตัวแปร Pointer มารับแอดเดรสก่อนทำคำสั่งเพิ่มหรือลดค่า (88:00)** * **การนำ Pointer ของ Array คนละตัวมาลบหรือเปรียบเทียบกัน** -> **เกิด Undefined behavior หรือได้ผลลัพธ์การคำนวณตำแหน่งที่ไม่ถูกต้อง** -> **ลบหรือเปรียบเทียบเฉพาะ Pointer ที่ชี้ไปยังสมาชิกภายใน Array ตัวเดียวกันเท่านั้น (117:00, 126:00)** * **การ Dereference ตัวแปร Pointer ที่เก็บค่าขยะหรือเป็น Null Pointer** -> **โปรแกรมแครชทันทีขณะทำงานหรือทำให้เกิด Undefined behavior** -> **กำหนดค่าเริ่มต้น Pointer เป็น `nullptr` เสมอ และตรวจสอบเงื่อนไขก่อนเรียกใช้งาน (140:00)** * **ลืมคืนหน่วยความจำที่จองไว้บน Heap (Memory Leak)** -> **พื้นที่ว่างบน Heap ค่อยๆ ลดลงเรื่อยๆ จนอาจส่งผลให้ RAM เต็มและโปรแกรมโดนปิด** -> **เรียกใช้คำสั่ง `delete` หรือ `delete[]` ทุกครั้งเมื่อใช้งานหน่วยความจำบน Heap เสร็จสิ้น (252:00)** * **เรียกคืนหน่วยความจำตำแหน่งเดิมซ้ำกันหลายครั้ง (Double-free)** -> **โปรแกรมเกิดแครชทันทีตอน Runtime หรือทำระบบจัดการหน่วยความจำเสียหาย** -> **เซ็ตค่า Pointer ให้เป็น `nullptr` ทันทีหลังเรียกใช้คำสั่ง Delete (158:00)** * **ใช้คำสั่ง `delete` แบบธรรมดาแทน `delete[]` ในการเคลียร์ Dynamic Array** -> **เกิด Undefined behavior หน่วยความจำรั่วไหล หรือฟังก์ชันทำลาย (Destructor) ไม่ถูกเรียก** -> **ใช้ `delete[]` เสมอกับหน่วยความจำที่ถูกจองผ่านคำสั่ง `new[]` (297:00)**📎 Repo Files¶
11.Loops/11.11DecrementingLoops/main.cpp12.Arrays/12.2DeclaringAndUsingArrays/main.cpp12.Arrays/12.3SizeOfAnArray/main.cpp12.Arrays/12.4ArraysOfCharacters/main.cpp12.Arrays/12.5ArrayBounds/main.cpp12.Arrays/12.8MultiDimensionalArrays/main.cpp