พอยน์เตอร์ใน C: พอยน์เตอร์ในการเขียนโปรแกรม C คืออะไร? ประเภท

⚡ สรุปอย่างชาญฉลาด

ตัวชี้ในภาษาซีเป็นตัวแปรที่เก็บที่อยู่หน่วยความจำของตัวแปรอื่น ช่วยให้สามารถจัดสรรหน่วยความจำแบบไดนามิก จัดการอาร์เรย์และสตริงได้อย่างมีประสิทธิภาพ และสร้างโครงสร้างข้อมูลที่ซับซ้อนได้ โดยใช้ตัวดำเนินการ address-of และ dereference เพื่ออ่านหรือแก้ไขค่า

  • 📌 ความหมาย: ตัวชี้ (pointer) เก็บที่อยู่หน่วยความจำของตัวแปรอื่น
  • 🔧 Operaทอร์: ตัวดำเนินการ & จะส่งคืนที่อยู่ ส่วนตัวดำเนินการ * จะเข้าถึงค่าที่ชี้โดยที่อยู่นั้น
  • 🧷 ประกาศ: เขียนชนิดข้อมูล *name จากนั้นกำหนดค่าเริ่มต้นเป็นที่อยู่ของตัวแปร
  • 🏷️ ประเภท: พอยน์เตอร์ที่เป็นค่าว่าง (null), พอยน์เตอร์ที่เป็นค่าไร้ตัวตน (void) และพอยน์เตอร์ที่เป็นค่าป่า (wild pointer) มีพฤติกรรมที่แตกต่างกันและต้องใช้ความระมัดระวัง
  • เลขคณิต: การเพิ่มค่าตัวชี้จะเลื่อนตัวชี้ไปตามขนาดของชนิดข้อมูลพื้นฐานของตัวชี้
  • 🤖 ความช่วยเหลือจาก AI: เครื่องมือ AI ช่วยแก้ไขข้อผิดพลาดในการแบ่งส่วนหน่วยความจำและแจ้งเตือนตัวชี้ที่ค้างอยู่ระหว่างการตรวจสอบ

ตัวชี้ในภาษาซี

ตัวชี้ (Pointer) ในภาษาซีคืออะไร?

A ตัวชี้ ในภาษาซี ตัวชี้ (pointer) คือตัวแปรที่เก็บที่อยู่ของตัวแปรอื่น ตัวชี้สามารถอ้างอิงถึงตัวชี้อื่นหรือฟังก์ชันก็ได้ ตัวชี้สามารถเพิ่มค่าหรือลดค่าเพื่อชี้ไปยังตำแหน่งหน่วยความจำถัดไปหรือก่อนหน้าได้ จุดประสงค์ของตัวชี้คือการประหยัดพื้นที่หน่วยความจำและทำให้การประมวลผลเร็วขึ้น

วิธีใช้พอยน์เตอร์ใน C

ถ้าเราประกาศตัวแปร v ชนิด int ตัวแปร v จะเก็บค่าไว้ ตัวแปรแต่ละตัว นอกจากค่าแล้ว ยังมีที่อยู่ด้วย นั่นคือตำแหน่งที่มันอยู่ในหน่วยความจำ สามารถเรียกดูที่อยู่ได้โดยการใส่เครื่องหมายแอมเปอร์แซนด์ (&) หน้าชื่อตัวแปร ถ้าคุณพิมพ์ที่อยู่ของตัวแปรออกมา มันจะมีลักษณะเหมือนตัวเลขสุ่ม ซึ่งอาจแตกต่างกันไปในแต่ละครั้งที่ทำงาน

แทนที่จะเก็บค่า ตัวชี้จะเก็บที่อยู่ของตัวแปร ตัวอย่างเช่น:

int *y = &v;
ตัวแปร ตัวชี้
A ความคุ้มค่า เก็บไว้ในไฟล์ ชื่อ ที่อยู่หน่วยเก็บข้อมูล/หน่วยความจำ A ตัวแปร ที่ จุด ที่อยู่การจัดเก็บ/หน่วยความจำของ อื่น ตัวแปร

ประกาศตัวชี้

เช่นเดียวกับตัวแปร ตัวชี้เข้า การเขียนโปรแกรม C ต้องประกาศตัวแปรชี้ก่อนจึงจะสามารถใช้งานได้ การประกาศตัวแปรชี้มีรูปแบบดังนี้:

data_type *pointer_variable_name;

ที่นี่ ประเภทข้อมูล คือชนิดข้อมูลพื้นฐานของตัวชี้ และบ่งบอกถึงชนิดของตัวแปรที่ตัวชี้ชี้ไป เครื่องหมายดอกจัน (*) ซึ่งเป็นสัญลักษณ์เดียวกับที่ใช้ในการคูณ เป็นตัวดำเนินการอ้างอิงที่ใช้ในการประกาศตัวชี้ ตัวอย่างการประกาศตัวชี้ที่ถูกต้องมีดังนี้:

int    *ptr_thing;       /* pointer to an integer */
int    *ptr1, thing;     /* ptr1 points to an int; thing is an int */
double *ptr2;           /* pointer to a double */
float  *ptr3;           /* pointer to a float */
char   *ch1;            /* pointer to a character */

เริ่มต้นตัวชี้

หลังจากประกาศตัวชี้แล้ว เราจะกำหนดค่าเริ่มต้นให้กับตัวชี้ด้วยที่อยู่ของตัวแปร หากตัวชี้ไม่ได้กำหนดค่าเริ่มต้นและถูกนำไปใช้ ผลลัพธ์อาจคาดเดาไม่ได้และอาจร้ายแรงได้ ในการรับที่อยู่ของตัวแปร ให้ใช้เครื่องหมายแอมเปอร์แซนด์ (&) นำหน้าชื่อตัวแปร:

pointer = &variable;

ตัวอย่างโปรแกรมอย่างง่ายที่แสดงวิธีการใช้งานตัวชี้มีดังต่อไปนี้:

#include <stdio.h>
int main()
{
   int a = 10;     // variable declaration
   int *p;         // pointer variable declaration
   p = &a;        // store address of a in pointer p
   printf("Address stored in p is: %x\n", p);  // access the address
   printf("Value stored in p is: %d\n", *p);    // access the value
   return 0;
}

Output:

Address stored in p is: 60ff08
Value stored in p is: 10
OperaTor ความหมาย
* ฟังก์ชันนี้มีสองวัตถุประสงค์ คือ ประกาศตัวชี้ และส่งคืนค่าของตัวแปรที่ถูกชี้ (การเข้าถึงค่าที่ชี้โดยตัวชี้)
& มีหน้าที่เดียวคือ ส่งคืนที่อยู่ของตัวแปร

ประเภทของพอยน์เตอร์ในภาษาซี

ตัวชี้ว่าง

เราสร้างพอยเตอร์ว่างโดยการกำหนดค่า NULL ให้กับมันในระหว่างการประกาศ ซึ่งมีประโยชน์เมื่อยังไม่มีการกำหนดที่อยู่ให้กับพอยเตอร์นั้น พอยเตอร์ว่างจะมีค่าเป็น 0 เสมอ:

#include <stdio.h>
int main()
{
   int *p = NULL;     // null pointer
   printf("The value inside variable p is: %x", p);
   return 0;
}

ตัวชี้โมฆะ

พอยเตอร์แบบ void หรือที่เรียกว่าพอยเตอร์ทั่วไป ไม่มีชนิดข้อมูลมาตรฐานใดๆ สร้างขึ้นโดยใช้คำหลัก void และสามารถเก็บที่อยู่ของตัวแปรใดๆ ก็ได้:

#include <stdio.h>
int main()
{
   void *p = NULL;    // void pointer
   printf("The size of pointer is: %d\n", sizeof(p));
   return 0;
}

ไวลด์พอยน์เตอร์

ตัวชี้ (pointer) ที่ไม่ได้กำหนดค่าเริ่มต้นให้ชี้ไปยังตำแหน่งใดๆ จะเรียกว่าตัวชี้ที่ไม่ถูกต้อง (wild pointer) ตัวชี้เหล่านี้ไม่มีประสิทธิภาพ เนื่องจากอาจชี้ไปยังตำแหน่งหน่วยความจำที่ไม่รู้จัก ซึ่งอาจก่อให้เกิดปัญหาและทำให้โปรแกรมหยุดทำงานได้ โปรดระมัดระวังในการใช้ตัวชี้ที่ไม่ถูกต้องเสมอ:

#include <stdio.h>
int main()
{
   int *p;          // wild pointer
   printf("%d", *p);
   return 0;
}

ตัวชี้ประเภทอื่นๆ ในภาษาซี ได้แก่ ตัวชี้ที่ชี้ไปยังตำแหน่งที่ว่างอยู่ (dangling pointer), ตัวชี้ที่ซับซ้อน (complex pointer), ตัวชี้ใกล้ (near pointer), ตัวชี้ไกล (far pointer) และตัวชี้ขนาดใหญ่ (huge pointer)

ตัวชี้การเข้าถึงโดยตรงและโดยอ้อม

ในภาษาซี มีสองวิธีที่เทียบเท่ากันในการเข้าถึงและจัดการเนื้อหาของตัวแปร การเข้าถึงโดยตรงใช้ชื่อตัวแปรโดยตรง ในขณะที่การเข้าถึงโดยอ้อมใช้ตัวชี้ไปยังตัวแปร:

#include <stdio.h>
int var = 1;        /* declare and initialize an int variable */
int *ptr;           /* declare a pointer to int */
int main(void)
{
   ptr = &var;       /* point ptr to var */
   printf("\nDirect access, var = %d", var);
   printf("\nIndirect access, var = %d", *ptr);
   *ptr = 48;        /* change var through the pointer */
   printf("\nIndirect access, var = %d", *ptr);
   return 0;
}

ตัวชี้เลขคณิตใน C

เมื่อทำงานกับตัวชี้ ให้สังเกตกฎลำดับความสำคัญเหล่านี้ ตัวดำเนินการ * และ & มีลำดับความสำคัญเท่ากับตัวดำเนินการเอกภาค (การปฏิเสธ !, การเพิ่มค่า ++, การลดค่า –) ในนิพจน์เดียวกัน ตัวดำเนินการเอกภาค *, &, !, ++ และ — จะถูกประเมินจากขวาไปซ้าย ถ้าตัวชี้ P ชี้ไปยังตัวแปร X แล้ว สามารถใช้ *P ได้ทุกที่ที่สามารถเขียน X ได้ นิพจน์ต่อไปนี้เทียบเท่ากัน:

การแสดงออก การแสดงออกที่เท่าเทียมกัน
Y = *P + 1 Y = X + 1
*P = *P + 10 X = X + 10
*P += 2 X += 2
++ป ++เอ็กซ์
(*ป)++ เอ็กซ์++

ในกรณีสุดท้าย จำเป็นต้องมีวงเล็บ เนื่องจากตัวดำเนินการเอกภาค * และ ++ จะถูกประเมินจากขวาไปซ้าย หากไม่มีวงเล็บ ตัวชี้ P จะถูกเพิ่มค่า ไม่ใช่วัตถุที่มันชี้ไป การดำเนินการทางคณิตศาสตร์ที่ใช้ได้กับตัวชี้ ได้แก่ การกำหนดค่า การเพิ่มค่าและการลดค่า และการบวกหรือการลบtracโดยการส่งค่าออฟเซ็ตที่เป็นจำนวนเต็ม ซึ่งจะเลื่อนตัวชี้ไปตามจำนวนองค์ประกอบนั้น (แต่ละขั้นเท่ากับขนาดเป็นไบต์ของชนิดข้อมูลพื้นฐาน)

ตัวชี้และอาร์เรย์ในภาษา C พร้อมตัวอย่าง

โดยทั่วไป เราเข้าถึงองค์ประกอบของอาร์เรย์โดยใช้ดัชนี แต่ตัวชี้ก็ทำให้การเข้าถึงง่ายขึ้นเช่นกัน ตัวชี้ที่กำหนดค่าเป็นชื่อของอาร์เรย์จะชี้ไปยังองค์ประกอบแรก:

#include <stdio.h>
int main()
{
   int a[5] = {1, 2, 3, 4, 5};   // array initialization
   int *p;                       // pointer declaration
   p = a;                       // p points to the first element; same as &a[0]
   for (int i = 0; i < 5; i++)
   {
      printf("%d\n", *p);            // print element
      p++;                      // move to the next element
   }
   return 0;
}

การเพิ่มตัวเลขให้กับตัวชี้จะทำให้ตัวชี้เลื่อนไปข้างหน้าตามจำนวนองค์ประกอบนั้น ถ้า p ชี้ไปยังตำแหน่งหน่วยความจำที่ 0 การเพิ่ม p + 1 จะทำให้ตัวชี้ชี้ไปยังตำแหน่งหน่วยความจำที่ 1

ตัวชี้และสตริง C พร้อมตัวอย่าง

สตริงคืออาร์เรย์ของอ็อบเจ็กต์ char ที่ลงท้ายด้วยอักขระว่าง '\0' เราสามารถจัดการสตริงโดยใช้พอยเตอร์ได้:

#include <stdio.h>
#include <string.h>
int main()
{
   char str[] = "Hello Guru99!";
   char *p;
   p = str;
   printf("First character is: %c\n", *p);
   p = p + 1;
   printf("Next character is: %c\n", *p);
   p = str;                     // reset the pointer
   for (int i = 0; i < strlen(str); i++)
   {
      printf("%c\n", *p);
      p++;
   }
   return 0;
}

อีกวิธีหนึ่งในการจัดการกับสตริงคือการใช้อาร์เรย์ของพอยเตอร์:

#include <stdio.h>
int main()
{
   char *materials[] = {"iron", "copper", "gold"};
   for (int i = 0; i < 3; i++)
      printf("%s\n", materials[i]);
   return 0;
}

ข้อดีของพอยน์เตอร์ในภาษาซี

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

ข้อเสียของพอยน์เตอร์ใน C

  • ตัวชี้เป็นสิ่งที่ซับซ้อนเล็กน้อยที่จะเข้าใจ
  • สิ่งเหล่านี้อาจทำให้เกิดข้อผิดพลาด เช่น ข้อผิดพลาดในการแบ่งส่วนหน่วยความจำ หรือการเข้าถึงตำแหน่งหน่วยความจำที่ไม่ต้องการ
  • การกำหนดค่าที่ไม่ถูกต้องให้กับตัวชี้อาจทำให้เกิดความเสียหายต่อหน่วยความจำได้
  • ตัวชี้ (pointers) ยังเป็นสาเหตุของการรั่วไหลของหน่วยความจำได้ หากหน่วยความจำนั้นไม่ได้รับการปล่อยให้ว่าง
  • เป็นหน้าที่ของโปรแกรมเมอร์ที่จะต้องจัดการกับตัวชี้อย่างระมัดระวัง

คำถามที่พบบ่อย

พอยเตอร์ที่ชี้ไปยังหน่วยความจำที่ถูกปล่อยไปแล้วหรืออยู่นอกขอบเขตการใช้งานเรียกว่า "พอยเตอร์แขวน" (dangling pointer) วิธีหลีกเลี่ยงคือตั้งค่าพอยเตอร์เป็น NULL ทันทีหลังจากเรียกใช้เมธอด free และตรวจสอบค่า NULL ก่อนใช้งาน

การเข้าถึงค่าที่ชี้โดยตัวชี้ที่เป็น NULL จะทำให้เกิดข้อผิดพลาดในการเข้าถึงหน่วยความจำ (segmentation fault) และทำให้โปรแกรมหยุดทำงานขณะรันไทม์ ดังนั้น ควรตรวจสอบให้แน่ใจเสมอว่าตัวชี้ไม่ใช่ NULL ก่อนที่จะใช้ตัวดำเนินการ * ในการอ่านหรือเขียนข้อมูล

ตัวชี้สองทาง (double pointer) ที่ประกาศเป็น int **pp จะเก็บที่อยู่ของตัวชี้อีกตัวหนึ่ง ใช้สำหรับอาร์เรย์สองมิติแบบไดนามิก และเพื่อแก้ไขค่าอาร์กิวเมนต์ที่เป็นตัวชี้ภายในฟังก์ชัน

ตัวชี้จะเก็บที่อยู่หน่วยความจำ และที่อยู่จะมีขนาดความกว้างเท่ากันในทุกแพลตฟอร์ม โดยทั่วไปคือ 4 ไบต์สำหรับระบบ 32 บิต และ 8 ไบต์สำหรับระบบ 64 บิต โดยไม่คำนึงถึงชนิดข้อมูลที่ชี้ไป

ตัวชี้ฟังก์ชันจะเก็บที่อยู่ของฟังก์ชันเพื่อให้สามารถเรียกใช้ได้แบบไดนามิก มีการใช้งานอย่างแพร่หลายสำหรับฟังก์ชันเรียกกลับ (callback) และสำหรับการสร้างตารางฟังก์ชันที่เลือกในระหว่างการทำงานของโปรแกรม

ฟังก์ชันต่างๆ เช่น malloc และ calloc จะส่งคืนตัวชี้ไปยังหน่วยความจำที่จัดสรรบนฮีปในระหว่างการทำงาน คุณเข้าถึงหน่วยความจำนั้นผ่านทางตัวชี้ และปล่อยตัวชี้ด้วย free เพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ

ใช่แล้ว ผู้ช่วย AI จะอ่านข้อผิดพลาด Segmentation fault และโค้ดโดยรอบ จากนั้นจะอธิบายสาเหตุที่เป็นไปได้ เช่น ตัวชี้ที่ไม่ได้เริ่มต้นหรือถูกปล่อยไปแล้ว และแนะนำวิธีแก้ไขให้คุณตรวจสอบ

เครื่องมือวิเคราะห์ที่ใช้ AI ร่วมกับเครื่องมืออย่าง Valgrind จะตรวจจับหน่วยความจำที่ไม่เคยถูกปล่อย และตัวชี้ที่ถูกใช้งานหลังจากถูกปล่อยแล้ว เครื่องมือเหล่านี้จะทำการตรวจสอบโดยอัตโนมัติและเน้นรูปแบบที่มีความเสี่ยงก่อนที่จะเผยแพร่โค้ด

สรุปโพสต์นี้ด้วย: