ข้ามไปที่เนื้อหา

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.cpp
  • 12.Arrays/12.2DeclaringAndUsingArrays/main.cpp
  • 12.Arrays/12.3SizeOfAnArray/main.cpp
  • 12.Arrays/12.4ArraysOfCharacters/main.cpp
  • 12.Arrays/12.5ArrayBounds/main.cpp
  • 12.Arrays/12.8MultiDimensionalArrays/main.cpp