Stack คืออะไร?
นั่นสิ Stack คืออะไร? มันคือพื้นที่พิเศษใน memory
(หรือบางคนแรียก RAM ก็ไม่ผิดซะทีเดียว) บนคอมพิวเตอร์ของคุณ
ที่จะเก็บค่าของตัวแปรที่ถูกสร้างไว้ในทุกๆ function (รวมถึง
function main() ในภาษา C ด้วย) แบบชั่วคราว
(ชั่วคราวหมายถึงเดี๋ยวค่าของตัวแปรก็หายไปได้)
Stack นั้นเป็นโครงสร้างข้อมูลที่เรียกว่า “FILO” (
first in, last out) ที่ถูกจัดการด้วย CPU อย่างรัดกุมสุดๆ
ทุกๆตัวแปร และทุกๆครั้งที่ตัวแปรถูกประกาศขึ้นภายใต้ function จะถูกเสียบเข้าไปใน stack ตามลำดับการสร้าง
และเมื่อฟังชั่นทำงานจบ ทุกๆตัวแปรใน function นั้นจะถูกปล่อยให้เป็นอิสระ
หรือ freed (ตามที่เราเข้าใจว่าตัวแปรถูกลบไป ถูกทำให้หายไป
แต่จริงๆแล้วมันคือการปล่อยให้เป็นอิสระ) เมื่อตัวแปรใน stack ถูกปลดปล่อย
พื้นที่บน memory ก็พร้อมที่จะให้ stack อื่นๆ ได้หยิบไปใช้งานต่อไป
ข้อได้เปรียบของระบบที่ใช้งาน stack ในการเก็บตัวแปรก็คือ memory จะถูกจัดการ โดยที่คุณไม่ต้องเหนื่อยมาก
เวลาจะใช้งานมันคุณก็ไม่ต้องมานั่งเขียนโค้ดจัดสรร หาพื้นที่ว่างบน memory หรือต้องคอยมานั่งลบตัวแปรที่ไม่ได้ใช้ด้วยตัวคุณเอง
และที่สำคัญกว่านั่นก็คือ เราบอกไปแล้วว่า CPU จะเป็นคนจัดการ memory
ดังนั้นมันจึงมีประสิทธิภาพสูง การอ่าน และการบันทึกตัวแปรลงใน memory
จึงมีความเร็วมากๆ
ถ้าจะหาวิธีจำการทำงานของ stack หละก็ ให้คิดว่า stack คือหลอดเมนทอส
เมื่อ function สร้างตัวแปรขึ้นมา ตัวแปรก็จะถูกเสียบลงในหลอดเหมือนเมนทอสถูกยัดลงไปในหลอดทีละเม็ดๆ เรียงลำดับกันไป และ
เมื่อ function ทำงานจบ ตัวแปรที่เคยเสียบลงไปใน stack ทั้งหมด จะเด้งออกมาจากหลอด (และตัวแปรก็หายไปตลอดกาล)
ดังนั้นตัวแปรใน stack จะเป็นตัวแปร
local โดยปริยาย
ซึ่งมันตรงกับคอนเซ็ปท์ของ
variable scope ที่เราอาจได้เคยเรียนในหนังสือหลายๆเล่ม ในเนื้อหาของ
ตัวแปรแบบ local vs ตัวแปรแบบ global
ซึ่งเป็น Bug
ยอดนิยมสำหรับโปรแกรมเมอร์มือใหม่ทั้งหลายที่พยายามจะเข้าถึงตัวแปรบน stack
ที่ได้ถูกประกาศใน function จากโค้ดบรรทัดที่อยู่นอก function นั้น
ลักษณะที่ควรจำไว้ให้ขึ้นใจของ stack ก็คือ มันมีขีดจำกัดของ size
(หรือขนาด หน่วยเป็นไบท์) ของตัวแปรที่จะถูกบรรจุลงใน
stack (ขึ้นอยู่กับ OS ของเครื่อง)
ข้อจำกัดนี้ไม่มีผลกับตัวแปรที่บันทึกลงในส่วนที่เรียกว่า
Heap
สรุปเรื่องของ stack:
- stack โตขึ้น ลดหดตัวลง ตามตัวแปรใน functions ที่เสียบเข้ามา และเด้งออกไป
- คุณไม่จำเป็นต้องมานั่งเขียนโค้ดจัดการ memory เองเพราะตัวแปรจะสร้างและถูกปล่อยให้เป็นอิสระอัตโนมัติ
- stack มีข้อจำกัดเรื่อง size ที่จะเก็บลงไป
- ตัวแปรใน stack จะมีชีวิตอยู่ และถูกเรียกใช้ได้ภายใต้ function ที่สร้างมันขึ้นมา เมื่อจบฟังชั่น ตัวแปรจะหายไป
ตัวอย่างนี้เป็นโค้ดสั้นๆในการสร้างตัวแปรบน
stack ซึ่งคุณอาจจะคุ้นเคยหน้าตาของมัน เพราะมันก็เหมือนโค้ดที่คุณเขียนกันประจำอยู่แล้ว
|
#include <stdio.h>
double multiplyByTwo (double input) {
double twice = input * 2.0;
return twice;
}
int main (int argc, char *argv[])
{
int age = 30;
double salary = 12345.67;
double myList[3] = {1.2, 2.3, 3.4};
printf("double your salary is %.3f\n", multiplyByTwo(salary));
return 0;
}
|
ผลลัพธ์
double your salary is 24691.340
บรรทัดที่ 10, 11 และ 12 เราได้สร้างตัวแปร int และ double และอาเรย์ของ doubles สามตัว
ตัวแปรเหล่านี้จะถูกเสียบลงใน stack ตามลำดับ ในตอนที่ฟังชั่น main() ได้สร้างมันขึ้นมา
และเมื่อ main() ทำงานจบลง (โปรแกรมก็จะหยุดทำงานเช่นกัน)
ตัวแปรเหล่านั้นจะถูกเด้งออกจาก stack คล้ายกันในฟังชั่น multiplyByTwo()
ที่มีตัวแปร twice เป็นชนิด double ที่จะถูกเสียบลงใน stack
เมื่อฟังชั่น multiplyByTwo() ได้สร้างตัวแปรขึ้นมา และต่อจากนั้น
ทันทีที่ multiplyByTwo() ทำงานจบลง ตัวแปร twice จะถูกเด้งออกจาก stack
และข้อมูลก็จะหายไปตลอดกาล
แต่ให้จำไว้ว่า หากต้องการเขียนภาษา C ให้เก็บตัวแปรไว้ แม้ function
ที่สร้างตัวแปรนั้นทำงานจบไปแล้ว ให้เราใช้คีย์เวิร์ด static
ในตอนประกาศตัวแปรนั้น
ตัวแปรใดที่ถูกประกาศพร้อมกับคีย์เวิร์ด static ตัวแปรนั้นจะหลายเป็นตัวแปรที่
คล้ายๆกับ
global variable คือสามารถใช้งานได้นอกขอบเขตของ function เพียงแต่เมื่อ
function ที่สร้างมันขึ้นมาทำงานจบแล้ว ตัวแปรนี้ก็จะถูกเด้งออกจาก stack
แล้วหายไปอยู่ดี
มันอาจจะดูเป็นโครงสร้างที่แปลกๆสักหน่อย แต่ในบางสถานการณ์ คุณก็อาจจะได้ใช้ประโยชน์กับมัน
ข้างล่างนี้เป็นโค้ดอีกเวอร์ชั่นของโปรแกรมตัวอย่างแรกซึ่งเราจะมาลองใช้
heap แทน stack กันดู
|
#include <stdio.h>
#include <stdlib.h>
double *multiplyByTwo (double *input) {
double *twice = malloc(sizeof(double));
*twice = *input * 2.0;
return twice;
}
int main (int argc, char *argv[])
{
int *age = malloc(sizeof(int));
*age = 30;
double *salary = malloc(sizeof(double));
*salary = 12345.67;
double *myList = malloc(3 * sizeof(double));
myList[0] = 1.2;
myList[1] = 2.3;
myList[2] = 3.4;
double *twiceSalary = multiplyByTwo(salary);
printf("double your salary is %.3f\n", *twiceSalary);
free(age);
free(salary);
free(myList);
free(twiceSalary);
return 0;
}
|
ตามโค้ดข้างบนจะใช้ malloc() เพื่อจอง memory บน heap และจากนั้นก็ใช้
free() เมื่อไม่ต้องการใช้ตัวแปรอีกแล้ว
จะเห็นว่ามันไม่ใช่เรื่องยากอะไรเลย แต่มันเป็นภาระที่ต้อง free()
หลังจากเลิกใช้ตัวแปรแล้ว
ให้สังเกตุอีกข้อหนี่งคือจะมีเครื่องหมายดอกจันทน์ * ตามตัวแปรไปทุกๆจุด ถ้าสงสัยว่ามันคืออะไร? คำตอบคือ มันเป็นเครื่องหมายของ
pointers
โดยคำสั่ง malloc() (และ calloc() กับ free() ) เป็นการทำงานกับ
pointers เสมอ ไม่ใช่การทำงานกับค่าตัวแปร เหมือนตัวแปร type พื้นฐานทั่วไป เราจะพูดถึงเรื่อง
pointers ในบทอื่น
ให้อธิบายคร่าวๆคือ pointers เป็นตัวแปรชนิดพิเศษในภาษา C ที่จะเก็บ addresses ของ memory
แทนที่จะเก็บค่าตัวตัวแปรตรงๆ แบบตัวแปรชนิดอื่น ให้สังเกตุบรรทัดที่ 5
จากตัวอย่างข้างบน ตัวแปร twice ไม่ได้เป็น double แต่มันเป็น pointer ที่เก็บตำแหน่ง หรือชี้เป้าไปยังของข้อมูลที่เป็น double ซึ่งข้อมูลที่ pointer ตัวนี้เก็บไว้ก็คือ ตัวเลขที่อยู่ (หรือ Address) ของ memory ที่ข้อมูล double ได้บันทึกไว้
เราควรใช้ Heap ตอนไหน?
คำถามคือคุณควรใช้ heap ตอนไหน และใช้ stack ตอนไหน?
ก็ถ้าเมื่อคุณต้องการใช้ memory ขนาดใหญ่ๆ เพื่อเก็บข้อมูลใหญ่ๆ (เช่น
array ที่มีสมาชิกเยอะๆ หรือ struct ที่เก็บข้อมูลมหาศาล)
และเมื่อคุณต้องการให้ตัวแปรนั้น คงอยู่ในระบบนานๆ (เหมือน global
veriable) ไม่หายไปหลังจบ function นั่นแหละที่คุณควรใช้ heap
และถ้าคุณต้องการใช้งานตัวแปรเล็กๆ และใช้งานภายใต้ function เฉยๆ
คุณก็ควรจะใช้งาน stack ถูกไหม เพราะนอกจากจะตอบโจทย์ที่ต้องการแล้ว
อย่าลืมว่า stack มันง่าย และเร็ว ไม่มี memory leak อีกด้วย
หรือถ้าคุณใช้ตัวแปรอย่าง arrays หรือ structs ที่สามารถ resize
ได้ตลอดเวลาแบบ dynamic (ก็เช่น arrays ที่เพิ่ม
และลดจำนวนเมื่อไหร่ก็ได้ที่ต้องการ) คุณก็ต้องใช้ heap อยู่แล้ว
และคุณก็ต้องเรียกใช้ malloc(), calloc(), realloc() และ free() เพื่อจัดการ จัดสรรหน่วยความจำให้ดี ไม่ให้เกิดตัวแปรขยะสะสมในระบบ
เดี๋ยวเราจะมีการพูดถึงการประกาศตัวแปรแบบ dynamically หลังจากบทของ pointers