วันศุกร์ที่ 11 มีนาคม พ.ศ. 2565

Gtk4 ตอนที่ 4 String and memory management

 ในการเขียนโปรแกรมไม่ว่าภาษาอะไรย่อมต้องเกี่ยวข้องกับข้อความตัวอักษร หรือชุดความที่เรียกว่า String แต่สำหรับภาษา C แล้วจะแตกต่างจากโปรแกรมอื่นๆ เล็กน้อย ตรงที่ไม่มีตัวแปรชนิด String แล้วจะมีตัวแปรชนิด char เพียงอย่างเดียว โดยตัวแปร char จะเป็น ASCII Code ดังนั้นการจัดเก็บจึงเป็นตัวเลขแบบ Unsigned Integer เช่น อักขระ a ในตาราง ASCII จะมีค่า 65 ดังนั้นถ้าเราพิมพ์ว่า a+1 จะมีค่าเป็น b หรือ 66 

ส่วนข้อความโดยทั่วไปนั้นในภาษา C จะเรียกว่า ชุดรวมอักขระ (Chain of character) คือ เป็นการนำเอาอักขระ (ตัวแปรชนิด char) มาต่อกันหลายๆ ตัวแล้วปิดท้ายด้วยอักขระว่าง หรือ \0 เป็นการสิ้นสุดข้อความ เช่น

char a[10] = "Hello";

เมื่อเรากำหนดแบบนี้ในระบบจะเก็บข้อมูลในเม็มโมรีเป็น

'H', 'e', 'l', 'l', 'o', '\0' 

ทั้งหมดมี 6 ตัวอักขระ ตรงนี้ต้องระวัง เพราะถ้าเรากำหนด a[5] = "Hello"; จะเสี่ยงต่อการเกิดข้อผิดพลาดทันที ตัวอย่างเช่น

  char a[5] = "Hello";
  char b[6] = "World";
  printf("%s, %s\n", a, b);

ตัวอย่างนี้จะพิมพ์คำว่า HelloWorld, World ออกทางหน้าจอ เพราะข้อความแรก ตัวแปร a ไม่มีรหัสสิ้นสุด ระบบจะอ่านไปเรื่อยๆ จนกว่าจะพบอักขระ \0 แต่ตัวแปร b 

นี่เป็นหลักการของ String ในภาษา C สำหรับ Gtk4 C ก็ไม่ได้แตกต่างอะไรมากมาย เพียงแต่ Gtk เป็น Linux Base ก็มีรูปแบบของตัวเอง เช่น มีตัวแปรชนิด gchar เพราะมีไลบรารี glib ซึ่งไม่มีในภาษา C มาตรฐาน ดังนั้นหากใครจะมองว่าไม่ใช่มาตรฐาน C Standard ก็ใช่ เพราะนี่เป็นโลกของ Gtk 

แต่โดยหลักการแล้ว String ในภาษา C (และ C บนลีนุกซ์) ก็จะมีดังนี้

  1. ข้อความที่อ่านได้อย่างเดียว
  2. ข้อความที่เป็น Array และ
  3. ข้อความที่อยู่ใน Heap

Read only string

ข้อความ หรือ  String literal ในภาษา C นั้นจะครอบด้วยเครื่องหมายคำพูด หรือ double quote เช่น

char *s;
char s = "Hello";

ตัวแปร s จะเป็น read only เราไม่สามารถเปลี่ยนแปลงข้อความใน s ได้ หากโปรแกรมพยายามเปลี่ยนแปลงก็จะได้รับ error ว่า Segmentation fault (core dumped) และระบบจะหยุดโปรแกรม เช่น

*(s+1) = 'a';

ตัวอย่างคำสั่งนี้จะพยายามเปลี่ยนตำแหน่งที่ +1 ของ s คือ อักขระตัวที่ 2 จาก e ให้เป็น a จะทำไม่ได้

แต่ถ้าเป็นตัวแปรแบบนี้ จะสามารถแก้ไขได้

char s[6] = "Hello";
*(s+1) = 'a';
printf("%s\n", s);

ผลที่ได้จะเป็น Hallo เพราะเราเปลี่ยนตำแหน่ง s+1 คือ ตำแหน่งที่ 2 ในที่นี้ คือ เปลี่ยน e เป็น a


Strings defined as arrays

ในการกำหนดตัวแปรข้อความหรือชุดตัวอักษรนั้นมีอีกแบบหนึ่งเรียกว่า array of character ซึ่งนำเอาตัวอักขระมาเรียงกันแล้วปิดท้ายด้วยเครื่องหมาย '\0' เช่น

char s[6] = "Hello";

การกำหนดแบบนี้ข้อความจะสามารถแก้ไขได้ เพราะตัวแปรนี้จะถูกเก็บไว้ใน Stack ซึ่งเป็นพื้นที่หน่วยความจำแบบอัตโนมัติ ไม่ต้องจองพื้นที่ เป็นแบบ LIFO หรือ เข้าทีหลังออกก่อน (Last In First Out) 

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

int main(){
  int i = 0;
  for (;i < 5; i++){
    char c = 65+i;
    printf ("%c", c);
  }
  printf ("%c", c);

  return 0;
}

ในฟังค์ชัน main จะมีลูป for(){ } อยู่อันหนึ่ง ภายใน for นี้จะมีประกาศตัวแปร char c ไว้ในลูป ตัวแปรแบบนี้เป็นตัวแปรที่เก็บไว้ใน stack เมื่อออกจากลูปไปแล้ว หลัง { } ของ for ตัวแปร c จะถูกทำลายไปหมดแล้ว ระบบจึงคอมไพล์ไม่ผ่านแล้วแจ้งออกมาทางหน้า ดังรูปด้านล่างนี้



ตัวอย่างข้างบนตัวแปร c จะสร้างใหม่และถูกทำลายอยู่ในลูปไปเรื่อยๆ จนกว่าจะหมดเงื่อนไข i < 5 และออกจาก loop ก็จะมองไม่เห็นตัวแปร c แล้ว


Strings in the heap area

ในภาษา C ที่เป็นมาตรฐานนั้นจะใช้ malloc และ free เพื่อจองและคืนหน่วยความจำ แต่สำหรับ Glib แล้วจะมี g_new และ g_free สำหรับจองและคืนหน่วยความจำ แต่ g_new จริงๆ แล้วเป็นมาโครเพื่อจองหน่วยความจำอีกทีหนึ่ง รูปแบบการใช้งาน คือ

g_new (struct_type, n_struct)
  • struct_type คือ ชนิดของอะเรย์ 
  • n_struct คือ ขนาดของอะไร
  • สิ่งที่ส่งค่ากลับก็จะเป็นพอยเตอร์หน่วยความจำที่จอง

ตัวอย่างเช่น

char *s;
s = g_new (char, 10);
/* s เป็น points ชี้ไปยังอะเรย์ชนิด char มีขนาดเท่ากับ 10 */

สำหรับ g_free เป็นการคืนค่าหน่วยความจำ มีรูปแบบการใช้งานดังนี้ 

void
g_free (gpointer mem);

คำสั่งข้างบนจะคืนหน่วยความจำตำแหน่ง gpointer mem ถ้า mem มีค่าเป็น NULL แล้ว g_free ก็ไม่ทำอะไร การใช้งานก็จะใช้หลังจากที่เลิกใช้ตัวแปร (ที่ประกาศด้วย g_new)

ในบางฟังค์ชันของ Glib ก็จะมีการจองหน่วยความจำใน Heap เช่นกัน ตัวอย่างเช่นฟังค์ชัน g_strdup จะจอง Heap แล้วคัดลอกข้อความที่กำหนดแล้วส่งค่าตำแหน่งคืนมา เช่น

char *s;
s = g_strdup ("Hello");
g_free (s);

ข้อความ "Hello" จะถูกจองใน Heap ไว้ 6 bytes เพราะระบบจะเพิ่ม '\0' ต่อท้ายให้ และส่งค่าตำแหน่งคืนไปยังตัวแปร s ซึ่งจะเป็นตัวแปรอะเรย์นั่นเอง ดังนั้นหลังจากเลิกใช้งานตัวแปรนี้แล้วต้องคืนหน่วยความจำตำแหน่งนั้นด้วย โดยใช้คำสั่ง g_free(s);


Some GLib functions return a string which mustn't be freed by the caller.

const char *
g_quark_to_string (GQuark quark);

This function returns const char* type. The qualifier const means that the returned value is immutable. The characters pointed by the returned value aren't be allowed to be changed or freed.

If a variable is qualified with const, the variable can't be assigned except during initialization.

const int x = 10; /* initialization is OK. */

x = 20; /* This is illegal because x is qualified with const */

เรียบเรียงจาก
https://github.com/ToshioCP/Gtk4-tutorial/blob/main/gfm/sec6.md



ไม่มีความคิดเห็น:

แสดงความคิดเห็น

Gtk4 ตอนที่ 6 Defining a Child object

Defining a Child object A Very Simple Editor ในบทความที่ผ่านมาเราสร้างโปรแกรมอ่านไฟล์ชนิดข้อความ และในบทความนี้ก็จะมาปรับแต่งโปรแกรมกันสักหน...