Sunday, November 8, 2015

Object-Oriented Programming Concepts

Object-Oriented Programming Concepts

หลักการการโปรแกรมเชิงวัตถุมีคุณลักษณะที่สำคัญอยู่ 3 ประการ
1. คลาส (Class)  คลาส ตามที่หลายๆคนต่างรู้จักว่าแบบพิมพ์ (template) หรือแม่พิมพ์ จริงแล้ว    คลาสคือโครงสร้างที่จะถูกนำไปใช้ในการสร้างวัตถุหรืออ็อบเจกต์ (object)  ในทางคณิตศาสตร์คลาส อาจเปรียบได้กับการเป็นเซต  ซึ่งมีสมาชิก (Instance) คือ อ็อบเจกต์ โดยอ็อบเจกต์มีข้อมูลที่เกิดขึ้นในระหว่างการประมวลผลโปรแกรม  คลาสมีพฤติกรรมซึ่งพฤติกรรมแสดงด้วยเมธอด (method) และมีคุณลักษณะหรือแอททริบิวต์ (attribute) ซึ่งแสดงด้วยตัวแปรต่างๆ  ซึ่งจะถูกกำหนดให้เป็นตัวแปรของคลาส (Class variable) หรือตัวแปรของอินสแตนส์ (Instance variable) โดยเมธอดก็จะถูกกำหนดให้เป็นเมธอดของคลาส หรืออินสแตนส์เช่นเดียวกัน 

public class Vehicle{
      public static  int  countInstance; //Class variable - ถูกสร้างสำหรับคลาสเท่านั้น
      public int gear;    //Instance variable - ถูกสร้างให้กับ Object หนึ่งๆ
      public int wheel;  //Instance variable - ถูกสร้างให้กับ Object หนึ่งๆ   
      public vehicle( ){ //Constructor - ถูกเรียกเมื่อมีการสร้าง Instance วัตถุประสงค์การใช้งานเพื่อ
                               //กำหนดค่าข้อมูลเริ่มต้นให้กับอ็อบเจกต์ 
             this.countInstance++;
      }
     public static void main(String[] args){  // ถูกสร้างให้กับคลาสเท่านั้น
            Vehicle  myFirstVehicle = new Vehicle();
            Vehicle  mySecondVehicle = new Vehicle();              
     }
     public void setGear(int g){  //ถูกสร้างให้กับอ็อบเจกต์
         this.gear = g; 
     }
     public void setWheel(int w){ //ถูกสร้างให้กับอ็อบเจกต์
         this.wheel = w; 
    }
}

Class variable ใน Java กำหนดด้วย static หากไม่มี static จะเป็น Instance variable
ใน Object ที่ถูกสร้างขึ้นจะไม่มีตัวแปร Class variable ดังนั้นจะไม่สามารถ access ไปยังตัวแปรที่เป็น class variable ได้
วัตถุประสงค์ของการใช้ Class variable ในโปรแกรม คือ ต้องการนับจำนวนของอ็อบเจ็กต์ที่ถูกสร้าง  เนื่องจากถูกๆครั้งที่คำสั่ง new ถูกเรียก จะเรียก Constructor ทำงานซึ่งจะทำการเพิ่มค่าของ countInstance เสมอ  ตัวแปร Class variable เสมือนมีเพียงหนึ่งสำเนาต่อหนึ่งคลาส  ส่วนตัวแปร Instance variable จะถูกสำเนาขึ้นสำหรับอ็อบเจ็กต์หนึ่งๆ

2. การถ่ายทอด (Inheritance) ในการสร้างคลาส  อาจมีความต้องการในการกำหนดความสัมพันธ์ระหว่างคลาสในลักษณะ IS-A relationship ซึ่งจะทำให้เกิดคลาสที่เราเรียกว่า Superclass และ Subclass โดย Superclass จะมีคุณลักษณะพื้นฐานของการเป็นคลาส  ส่วน Subclass จะมีคุณลักษณะที่เฉพาะมากขึ้น  ซึ่งบางคุณลักษณะได้รับการถ่ายทอดจาก Superclass
คุณลักษณะเฉพาะของ Subclass เกิดจาก
- การเพิ่มแอททริบิวต์ให้กับคลาส  (เพิ่มตัวแปรต่างๆ)
- การเพิ่มพฤติกรรมให้กับคลาส (เพิ่มเมธอด) อาจมีการสร้าง Overloaded method (เมธอดที่มีชื่อเดียวกันแต่มีความแตกต่างของ Signature (Formal parameter และ/หรือ Return type)
- การแก้ไขเมธอด (อาจเรียกว่าเขียนทับ) ซึ่งหมายถึงการแก้ไขโค้ดของเมธอดที่ได้รับการถ่ายทอดมากจาก Superclass

3. Polymorphism (ภาษาไทยไม่ทราบว่าจะใช้คำว่าอะไรดี) ในเรื่องของ Polymorphism นั้น เกี่ยวข้องกับการสร้าง Polymorphic variable หรือตัวแปรอ็อบเจกต์ที่ถูกสร้างขึ้นจาก Descendant class (คลาสที่เป็น subclass หรือคลาสที่อยู่ต่ำกว่าใน Hierarchy ที่แสดงความสัมพันธ์ IS-A ของคลาส)   แทนที่จะสร้างอ็อบเจกต์จากคลาสโดยตรงก็ไปสร้างจากคลาสที่อยู่ในระดับต่ำกว่า  เนื่องจากโดยหลักการ Object-Oriented Programming นั้นจะมีข้อสมมติหนึ่งว่าในเมื่อ Superclass มีคุณลักษณะพื้นฐานของคลาสซึ่งถูกถ่ายทอดให้กับ Subclass แล้วดังนั้นก็สามารถที่จะสร้าง Instance ของคลาสจากคลาสที่เป็น Subclass หรือคลาสที่อยู่ต่ำกว่าลงไปได้เช่น

class Vehicle{
    ...
}
class MotorVehicle extends Vehicle
{
   ...
}

class Car extends MotorVehicle{
  ...
}

public class MyFactory{
      public static void main(String[] args){
          Vehicle  myVehicle = new Car();
          MotorVehicle   myCar = new Car();       
      }
}
อ็อบเจกต์ myVehicle และ myCar คือ Polymorphic variable จะมีคุณลักษณะพื้นฐานตามคลาสที่กำหนดด้วยคำสั่ง new อย่างไรก็ตามการสร้าง message อาจเรียกไปยังเมธอดของคลาสซึ่งอ็อบเจกต์นั้นเป็นสมาชิกอยู่ก็ได้   ซึ่ง myVehicle เป็นสมาชิกของคลาส Vehicle ส่วน myCar เป็นสมาชิกของคลาส MotorVehicle
การเกิด Polymorphism นี้ทำให้เกิด Dynamic Binding คือการทำเมสเสสไปยังเมธอดของคลาสที่เกี่ยวข้อง 


Wednesday, July 22, 2015

Variable และ Storage binding

Variable และ Storage binding

ตัวแปรจะผูกมัดกับตำแหน่งในเมมโมรี ในระหว่างการประมวลผล
ประเภทตัวแปรเมื่อพิจารณาลักษณะการผูกมัดไปยังเมมโมรี สามารถจำแนกได้ดังนี้
1. Static variable ตัวแปรพวกนี้จะผูกมัดไปยังตำแหน่งในเมมโมรีในช่วง Load time เช่น ตัวแปรที่กำหนดด้วยคำว่า static เช่น ในภาษา C
static int c;
การใช้งานตัวแปร static ในฟังก์ชั่น main และ ในฟังก์ชั่นอื่นๆ จะมีความแตกต่างกัน แต่การผูกมัดจะกระทำใน load time ก่อน runtime
2. Stack-Dynamic variable คือตัวแปรที่ถูก allocated ในช่วง run time เช่น การใช้งานตัวแปรปกติในภาษา c เช่น int c;
3. Explicit Heap-Dynamic Variables ตัวแปรผูกมัดกับเมมโมรีที่มีการจัดการโครงสร้างการเข้าถึงในลักษณะ Heap  เช่นในภาษา Java คำสั่ง new คือคำสั่งในการ allocate เมมโมรีสำหรับ Object หรือใน C++ เช่นเดียวกัน Allocation และ Deallocation จะกำหนดด้วยคำสั่งในภาษาการโปรแกรม  (่Java มี garbage collection ซึ่ง deallocation ทำอัตโนมัติเมื่อไม่มีการเรียกใช้ Object แล้ว)
4. Implicit Heap-Dynamic Variables  ตัวแปรจะผูกมัดไปยังตำแหน่งในเมมโมรีเมื่อมีการกำหนดค่า (การทำ assignment) เช่นในภาษาประเภท Dynamic typing (เข่น PHP, JavaScript) ที่ไม่ต้องประกาศไทป์ของตัวแปรก่อนการใช้งาน  จะเกิดการทำ allocation เมื่อรันถึงคำสั่งที่มีการกำหนดค่าให้กับตัวแปร

Subprogram

Subprogram
ในหลักการภาษาการโปรแกรมนั้น Subprogram หมายถึงส่วนของโปรแกรมที่มีการเรียกใช้ซ้ำ  มี 2 ประเภท คือ
1. Function คือ subprogram ซึ่งจะมีการรีเทิร์นค่าข้อมูล
2. Procedure คือ subprogram ซึ่งไม่มีการรีเทิร์นค่ากลับ
เมื่อรันโปรแกรมและมีการเรียก Subprogram จะทำให้การประมวลผลของโปรแกรมหลักหยุดชั่วคราว เพื่อเข้าไปประมวลผลใน Subprogram  ซึ่งจะมีการสร้างพื้นที่ในเมมโมรีเพื่อจัดการกับโค้ดและข้อมูลของ Subprogram ดังภาพ พื้นที่ส่วนแรกเก็บโค้ดของ Subprogram และพื้นที่ส่วนถัดไปเก็บ data ที่มีการประมวลผลใน Subprogram ซึ่งเรียกว่า Activation record (ดูภาพ 10.2)
Nested subprogram หมายถึง Subprogram ที่ซ้อน Subprogram บางภาษาเอื้อในการเขียนโปรแกรมในลักษณะนี้ เช่น  JavaScript

function sub1() { 
    var x; 
       function sub2() { 
              alert(x); // Creates a dialog box with the value of x 
        }; 
       function sub3() { 
                var x; 
                 x = 3; 
                sub4(sub2); 
        }; 
       function sub4(subx) { 
                var x; x = 4; subx(); 
         }; 
  x = 1; 
  sub3(); 
 };

ภาษาการโปรแกรมต้องกำหนดวิธีการเรียก Subprogram ในกรณีที่เป็น Nested subprogram ภาษาส่วนใหญ่จะยอมให้เรียก Subprogram ย่อยภายใน Nested subprogram เมื่อ Nested subprogram ถูกเรียกก่อน เช่น Main program ต้องเรียก sub1( ) ก่อน  จึงจะสามารถเรียก Subprogram อื่นๆภายใน 
และภายใน Nested subprogram นั้นอาจมีการรเรียก Subprogram ย่อยภายใน  ซึ่งจะต้องพิจารณา Reference environment ตามไปด้วย  


เมื่อมีการเรียก Subprogram ทำงาน สิ่งที่ต้องคำนึงถึงได้แก่ 
1. Referencing environment ในคำสั่ง/บรรทัดหนึ่งๆ ใน Subprogram จะสามารถมองเห็น(ใช้งาน) ตัวแปรใดบ้าง?
2. Parameter passing mode เป็นอย่างไร ? 
3. เมื่อมีการเรียก Subprogram แล้วการจัดการ Scope ของ Subprogram จะเป็นอย่างไร  ?

ในกรณี Nested subprogram ต้องพิจารณา Environment ของ Subprogram ที่ถูกเรียก ซึ่งพิจารณาจาก 3 ลักษณะ
- Shallow binding เมื่อมีการเรียก subprogram แล้ว environment ของ subprogram ที่ถูกเรียกจะเป็น Environment ของ subprogram ที่เรียก เช่น  sub4(sub2)  environment ของ sub2 คือ Environment ของ sub4 ดังนั้น เมื่อ alert ทำงานจะแสดงผลลัพธ์ x ของ sub4 
- Deep binding  เมื่อมีการเรียก subprogram แล้ว environment ของ subprogram ที่ถูกเรียกจะเป็น Environment ตาม definition ของ subprogram เช่น sub4(sub2) environment ของ sub2 คือ sub1 เนื่องจากมันอยู่ภายใต้ sub1 ดังนั้น alert จะแสดง x ของ sub1    
- Adhoc binding เมื่อมีการเรียก subprogram แล้ว environment ของ subprogram ที่ถูกเรียกจะเป็น Environment ของตำแหน่งที่เรียก subprogram เช่น sub4(sub2)  นั้นอยู่ใน sub3 ดังนั้น sub2 จะแสดงค่า x  ของ sub3 

Coroutine 
Coroutine คือ subprogram ประเภทหนึ่ง ที่สามารถเข้าถึงได้หลายทางหรือมี Multiple entry point การเรียก coroutine จะเกิดความสัมพันธ์ระหว่าง  subprogram ในลักษณะ Master-slave  คำสั่ง resume ทำให้เกิดการเรียก  subprogram โดยเข้าถึง subprogram ในตำแหน่งคำสั่งถัดจากคำสั่งที่ออกจาก subprogram จากการเรียกครั้งก่อน (ในกรณีเรียก subprogram ครั้งแรกจะเป็นการเรียกไปยังจุดเริ่มต้นของ subprogram)

sub co1(){ 
         . . . 
         resume co2(); 
         . . . 
         resume co3();
         . . . 
         resume co2(); 
}

sub co2(){ 
         . . . 
         resume co1();
        ...........
}
sub co3(){ 
         . . . 
         resume co1(); 
}
 


Monday, July 20, 2015

Scope และ Referencing Environment

Scope และ Referencing Environment

Scope การใช้งานตัวแปร คือ ขอบเขตที่สามารถใช้งานตัวแปรได้
การกำหนด Scope จำแนกได้ 2

ลักษณะ
1. Static Scoping  ตัวแปรจะมีขอบเขตสัมพันธ์กับ Unit ที่ครอบตัวแปร  การใช้งานตัวแปรสามารถทำได้หลังจากมีการประกาศ (declare) ตัวแปรแล้ว (สำหรับภาษาที่ต้องประกาศไทป์ก่อนใช้)
หรือในบางภาษาที่ไม่ต้องประกาศไทป์ก่อนใช้งาน ก็จะสามารถเรียกใช้ได้ทันทีตั้งแต่บรรทัดที่มีการใช้งานตัวแปร
เช่น ใน C มีการจัดการแบบ Static scoping
1.  |  #include <stdio.h>
2.  | int x;
3.  | void main( )
4.  | {
5.  |      void  myfunction(int a, int b);
6.  |      int y;
7.  |    ...
8.  | }
9.  | void myfunction (int m, int n)
10.| {
11.|      int o;
12.|     ...
13.| }

Unit หรือขอบเขต ในโปรแกรมสามารถพิจารณาได้ดังนี้
1. ขอบเขตครอบคลุมทั้งโปรแกรม
2. ขอบเขตภายในฟังก์ชั่น main
3. ขอบเขตภายในฟังก์ชั่น myfunction

ตัวแปร  x ในภาษา C จึงสามารถมองเห็นได้จากทุกๆส่วนของโปรแกรม  หรือเราจะรู้ตามกฎการเป็น Global variable ในภาษา C ที่อะไรประกาศเหนือฟังก์ชั่น main จะมองเห็นได้จากทุกส่วนของโปรแกรม main และ subfunction
ตัวแปร a และ b เป็นเพียงตัวแปรที่ประกาศตามข้อกำหนดของฟังก์ชั่นไม่สามารถใช้งานได้
ตัวแปร y ประกาศในฟังก์ชั่น main จึงสามารถเรียกใช้ได้ภายในฟังก์ชั่น main โดยนับตั้งแต่คำสั่งถัด (หรือบรรทัดถัดไป) หลังประกาศตัวแปรแล้ว แต่ตัวแปร y ไม่สามารถมองเห็นได้ในฟังก์ชั่น
ตัวแปร m, n  และ o ถือว่าเป็นตัวแปรที่ประกาศในฟังก์ชั่น ดังนั้นสามารถใช้งานได้ในเวลารันที่มีเรียกฟังก์ชั่นเท่านั้น และจะเรียกตัวแปร o ได้ในบรรทัดถัดไป (หลังบรรทัดที่ประกาศ)

ข้อสังเกต
1. การจัดการขอบเขตการใช้งานของตัวแปรในลักษณะนี้มักจะกำหนดให้ตัวแปรที่ประกาศใน Unit ที่ให,่กว่า สามารถมองเห็นได้ใน Unit ที่เล็กกว่า เช่น กรณีตัวแปร x เป็นต้น
2. เมื่อตัวแปรมีขอบเขตของตัวแปร  เราจะเรียกว่าเป็น Local variable ของ Unit นั้น  ส่วนตัวแปรอื่นๆที่อยู่นอกจาก Unit นั้น เราจะเรียกว่า Nonlocal variable   
3. บางภาษาการโปรแกรมกำหนดให้การประกาศตัวแปรต่างๆ ต้องทำก่อนคำสั่งอื่นๆ โดยไม่ยอมให้มีการประกาศในระหว่างโปรแกรม   แต่บางภาษาอนุญาตให้มีการประกาศได้ในตำแหน่งใดก็ได้ของโปรแกรม เพียงแค่กำหนดขอบเขต เช่นในภาษา C ใช้เครื่องหมาย {...} กำหนดขอบเขตของตัวแปร
    {
        int i;
        i = 88;
        j = 99;
        printf("(2) i: %d, j: %d\n", i, j);
    }


2. Dynamic Scoping
การจัดการขอบเขตการใช้งานของตัวแปรขึ้นกับลำดับการเรียกฟังก์ชั่น
สมมติภาษา Anynomous มีการจัดการแบบ Dynamic Scoping

1.  |int  b = 5;
2.  |int foo( )
3.  |{
4.  |    int a = b + 5;
5.  |    return a;
6.  |}
7.  |int bar( )
8.  |{
9.  |    int b = 2;
10.|    return foo( );
11.|}
12.|int main( )
13.|{
14.| foo( );
15.| bar( );
16.| return 0;
17.| }

บรรทัดที่ 12 เมื่อมีการรันโปรแกรมฟังก์ชั่น main( ) (สมมติ main ถูกเรียกก่อน) ถูกเรียกก่อน
ตัวแปร b จะถูกมองเห็นเป็นอันดับแรก
บรรทัดที่ 14 เมื่อเรียกฟังก์ชั่น foo จะมองเห็นตัวแปร a และ b ซึ่ง a = 10 และ b = 5 แล้วรีเทิร์นออกจากฟังก์ชั่นไปบรรทัดที่ 15
บรรทัดที่ 15 จะมองเห็นตัวแปร a และ b ดังนั้นเมื่อเรียกฟังก์ชั่น bar การประกาศ b ขึ้นมาใหม่จะเป็น b อีกตัวแปรหนึ่ง ซึ่งมีค่าเท่ากับ 12 และยังคงมี b = 5 ในสแตก แต่ไม่สามารถเรียกใช้ b = 5 ภายใน bar( บรรทัดที่ 9-11)  มีการเรียก  foo อีกครั้ง (จากการเรียก return ) จะเกิด a อีกตัว (บรรทัดที่ 4) ซึ่งตอนนี้ a มีค่าเท่ากับ 7 (จาก b = 2 ใน bar) ส่งค่ากลับคือ 7 แล้วในบรรทัดที่ 10 จะส่งค่ากลับคือ 7 แล้วรีเทิร์นไปทำงานที่บรรทัดที่ 16
 

ถ้าแก้ไขโค้ดใหม่ (ยังคงเป็นภาษา Anynomous ไม่ใช่ภาษา C!!)
12.|int main( )
13.|{
14.|  ...
15.| foo( );
16.| ......
17.| bar( );
18.| ......
19.| return 0;
20.| }

บรรทัดที่ 14 จะมองเห็นตัวแปร b 
บรรทัดที่ 16 เมื่อเรียก foo แล้วจะเห็นตัวแปร  b = 5 และ a = 10
บรรทัดที่ 18 เมื่อเรียก bar แล้วจะเห็นตัวแปร  b = 2 และ a = 7 
ขอบเขตของตัวแปรขึ้นกับลำดับการเรียกฟังก์ชั่น ไม่ใช่ขอบเขตของ Unit จากภาพด้านบน  ยังคงมี a = 10 และ b = 5 ในสแตก แต่การรันโปรแกรมจะไม่สามารถเรียกใช้ตัวแปรเหล่านี้ได้ เพราะถูกบดบังด้วยตัวแปร a และ b ที่เกิดขึ้นล่าสุดจากการประกาศในบรรทัดที่ 4 และ 9 ในการเรียก foo และเรียก bar (ซึ่งเรียก foo ต่อเพื่อรีเทิร์นค่า) 

ข้อสังเกต
ยังต้องพิจารณาจากขอบเขตของตัวแปรจาก Unit ที่ตัวแปรอยู่เช่นเดียวกับ Static scoping แต่พิจารณาเพิ่มเมื่อมีการเรียกฟังก์ชั่น


Referencing environment

Referencing environment หมายถึง ชื่อตัวแปรต่างๆที่มองเห็นได้จากบรรทัดที่กำลังประมวลผล
จากโค้ดที่ยกตัวอย่างบนสุด  สมมติรันโปรแกรมมาถึงบรรทัดที่ 6 ณ บรรทัดนั้นจะสามารถมองเห็นตัวแปร  y และตัวแปร x  ได้  ซึ่งทั้งสองตัวแปรคือ  Referencing environment ของการประมวลผล ณ ขณะนั้น
หากรันโค้ดมาถึงบรรทัดที่ 11 Referencing environment คือ ตัวแปร m, n, o และ x พารามิเตอร์ m และ n ถือว่าเป็น local variable ของฟังก์ชั่น

Sunday, July 19, 2015

Binding และ Binding time

Binding และ Binding time
Binding คือ การผูกมัดสิ่ง 2 สิ่งเข้าด้วยกัน เช่น ไทป์กับตัวแปร โอเปอเรเตอร์กับการดำเนินการ ตัวแปรกับตำแหน่งอ้างอิงในเมมโมรี
Binding เกิดได้ตลอดเวลา  อาจพิจารณาเวลาผูกมัดได้หลายเวลา เช่น
1. Language design time
2. Language implementation time
3. Compile time
4. Link time
5. Load time
6. Run time

Language design time 
การผูกมัดที่เกิดในช่วงการออกแบบภาษา  เช่น การออกแบบชิ้นส่วนต่างๆ (construct) ของภาษา เช่น สัญลักษณ์ตัวดำเนินการแทนการดำเนินการ (+ แทนการบวก, - แทนการลบ เป็นต้น)  รูปแบบ Statement (if-else, while, for) ไทป์ต่างๆ

Language implementation time 
การผูกมัดที่เกิดในเวลานำไปใช้ เกี่ยวข้องกับการกำหนดรูปแบบของ Precision ของ Floating-point number และ การแสดงในเมมโมรีของ data type ชนิดต่างๆ  (ใช่กี่บิต  เรียงบิตอย่างไร เลขยกกำลังใช้มาตรฐานอะไร) การจัดการเมมโมรีของโปรแกรม หรือฟงัก์ชั่น การจัดการเมมโมรีของตัวแปร local และ nonlocal การจัดการตรวจจับรันไทม์ error เช่น Overflow

Compile time 
การผูกมัดที่เกิดในเวลาคอมไพล์ เช่น การจับคู่ระหว่าง Token กับ Lexeme ในตาราง Symbol table เช่น ตัวแปรกับไทป์ เครื่องหมายดำเนินการกับตัวดำเนินการ  หรือการผูกมัดระหว่างตัวดำเนินการกับ Machine code (เช่น + ผูกมัดกับคำสั่ง ADD ของ Machine code รุ่น intel....)

Link time 
ในการเขียนโปรแกรมบางครั้งอาจมีการแยกโปรแกรมออกเป็นส่วนๆ แล้วแยกคอมไพล์  ในเวลานี้คือเวลาที่เกิดขึ้นเพื่อนำโค้ดที่ผ่านการคอมไพล์มารวมเข้าด้วยกัน  ก่อนที่จะสร้าง Machine code (ซึ่งจะมีเพียงส่วนเดิยวที่สามารถนำไปรันได้)  ยกตัวอย่างในภาษา C เราอาจแยกโค้ดเป็นหลายส่วน เมื่อคอมไพล์จะได้ .OBJ แล้วในเวลา Link time คือผููกมัดที่นำ .OBJ มารวมกัน  นอกจากนี้แล้วอาจพิจารณาการรวมโค้ดของโปรแกรมเข้ากับโค้ดของ OS เพื่อใช้งาน I/O ได้อีกด้วย

Load time 
คือเวลาผูกมัดที่เกิดขึ้นเมื่อ OS โหลดโปรแกรมเข้าไปไว้ในเมมโมรีก่อนรัน  ตัวอย่างเช่น การโหลดตัวแปรประเภท Static จะเกิดการผูกมัดไปยัง Physical address ตำแหน่งหนึ่ง ตลอดการรันโปรแกรม
(ดูการใช้งานตัวแปร Static) อะไรก็ตามที่ถูกกำหนดผูกเข้ากับ Physical address เกิดการผูกมัดในเวลา load time เช่น ค่าคงที่ต่างๆเช่นกัน

Runtime 
การผูกมัดที่เกิดในเวลารัน เช่น ตัวแปรกับค่าของตัวแปร  ตัวแปร (dynamic variable) กับตำแหน่งในเมมโมรี (ซึ่งอาจเปลี่ยนไปได้ตลอดเวลา)

คำสั่งหนึ่งคำสั่งอาจเกี่ยวข้องกับการผูกมัดได้หลายลักษณะ เช่น

int  x = 10; 

ค่าคงที่ 10 มีการผูกมัดไปยังตำแหน่งในเมมโมรี  (อาจอยู่ในรีจิสเตอร์ก็ได้) ในเวลา Load time
ตัวแปร x มีการผูกมัดไปยังไทป์ในเวลาคอมไพล์
ส่วนตัวแปร x จะผูกมัดไปยังตำแหน่งในเมมโมรี (หรือรีจิสเตอร์) ของค่าคงที่ 10 จะรู้ก็ตอนรันไทม์ (ไม่รู้ตอนคอมไพล์เพราะช่วงคอมไพล์ x ยังไม่ผูกมัดไปยัง Physical address แต่อาจผูกไป virtual address ก็ได้ถ้าภาษากำหนดขึ้นมาชั่วคราว)

อาจเป็นการยากที่จะพิจารณาว่าการผูกมัดเกิดในช่วงใด  ทั้งนี้ต้องวิเคราะห์กันในแต่ละภาษา เพราะแต่ละภาษาการโปรแกรมอาจมีการจัดการที่แตกต่างกันในบางเรื่อง
  

Semantics ของโปรแกรม

Semantics ของโปรแกรม
การบรรยาย Syntax ของโปรแกรมมักจะง่ายกว่า Semantics ของโปรแกรม Semantics ของโปรแกรม  บ่งบอกความหมายในการทำงานของนิพจน์ ประโยค หรือ โปรแกรม
ในกระบวนการคอมไพล์ในขั้นตอนของ Syntax analysis นั้น เป็นการตรวจสอบความถูกต้องของโปรแกรมตามรูปแบบของภาษาที่กำหนด  โดยอาศัย Symbol table ก่อนที่คอมไพเลอร์จะสร้าง Intermediate code จะมีการกระบวนการ Semantic analysis คือ กระบวนการค้นหาความหมายของการทำงานของโปรแกรม  เช่น
1. การตรวจสอบว่า Identifier ทุกตัวมีการประกาศไทป์ (สำหรับภาษาการโปรแกรมประเภท Static type binding)
2. การตรวจสอบ Type checking (การนำไทป์ไปใช้กับ Operator มีความถูกต้องหรือไม่)
3. การเรียก subprogram มีการกำหนดพารามิเตอร์ครบถ้วน และไทป์ถูกต้องกับการประกาศฟังก์ชั่น
4. ใน Statement มีการกำหนดค่า Label ที่ถูกต้องเช่น คำสั่ง switch นั้นใน case จะต้องเป็น Integer เท่านั้น
5. ฟังก์ชั่นที่มีการรีเทิร์นค่ากลับต้องมีคำสั่ง return ทุกฟังก์ชั่น
อย่างไรก็ตามการตรวจสอบ error ตาม semantic analysis ก็ยังไม่สามารถตรวจ error ได้ทุกอย่าง  ฉะนั้นการตรวจสอบ error บางลักษณะจะมีการตรวจสอบใน runtime (เวลารันโปรแกรม)  
เช่น subscript ของอะเรย์ต้องไม่อ้างอิงเกินขนาดของอะเรย์ (error: Array index out of bound) พอยน์เตอร์ไม่เคยถูกใช้งานในการอ้างอิงถึงแม้มีการกำหนดพอยน์เตอร์ หรือตัวแปรไม่เคยถูกใช้งาน และการประมวลผลจะต้องไม่เกิด Overflow

มีวิธีการหลายอย่างในการกำหนด Semantics ของภาษา เช่น สร้าง Attribute Grammar, Operational semantics, Denotational semantics, และ Axiomatic Semantics

ในที่นี้ขอยกตัวอย่าง Attribute Grammar ซึ่งอาจกำหนดควบคู่กระบวนการสร้าง CFG Grammar
เช่น

1. Syntax rule: <assign> → <var> =  <expr>
    Semantic rule: <expr>.expected_type ← <var>.actual_type 
แสดงความหมายการนำไทป์ของตัวแปร ให้กับ ไทป์ผลลัพธ์จากการประเมินผลนิพจน์
2. Syntax rule: <expr>→ <var> [2] +  <var> [3] 
   Semantic rule: <expr>.actual_type ← if (<var>[2].actual_type = int) and 
                                                                       (<var>[3].actual_type = int) 
                                                                  then int else real end if 
 Predicate: <expr>.actual_type == <expr>.expected_type
 แสดงความหมายไทป์ของนิพจน์ที่ประเมินผล คือไทป์ integer หากไทป์ของ <var>[2] และ <var>[3] คือ integer  และมีไทป์เป็น real ในกรณีอื่นๆ

3. Syntax rule: <expr>→ <var>
    Semantic rule: <expr>.actual_type ← <var>.actual_type 
    Predicate: <expr>.actual_type == <expr>.expected_type 
 แสดงความหมายไทป์ของ <var> ถูกกำหนดให้กับ <expr>  
4. Syntax rule: <var> → A | B | C 
    Semantic rule: <var>.actual_type ← look-up(<var>.string)
 แสดงความหมายไทป์ของตัวแปรที่ค้นได้จาก symbol table ถูกนำไปให้กับตัวแปร 

actual type ค่าไทป์ที่เกี่ยวข้องกับ nonterminal <var> และ <expr>
expected type ค่าไทป์ที่คาดว่า expression จะได้ หรือไทป์นี้เกี่ยวข้องกับ <expr>

จาก Attribute grammar จะแสดง Parse tree ได้ดังภาพ 3.6 ซึ่งกฎข้อ 1 - 4 จะนำเข้าใช้ตรวจสอบ Type checking

อีกวิธีการหนึ่งที่น่าสนใจคือ Axiomatic Semantics ศึกษาต่อในแบบฝึกหัด



ปัญหา Side effect

ปัญหา Side effect และ Referential Transparency

ในการเขียนโปรแกรมที่เกี่ยวข้องกับนิพจน์  ในบางครั้งก็อาจเกิดปัญหา Side effect (ผลกระทบซึ่งเราไม่ทราบ)  ปัญหานี้จะทำให้โปรแกรมเมอร์ debug โปรแกรมไม่ได้ หากไม่เข้าใจถึงผลกระทบที่เกิดขึ้น  ปัญหานี้มักเกี่ยวข้องกับเรียกใช้งานฟังก์ชั่นในโปรแกรมซึ่งเราเรียกว่าเกิด Functional side effect
นิพจน์ (expression)
    result  = A * B + myfunction( ) + 20;
 
คำถามคือ
1. เมื่อนิพจน์ (expression) มีการเรียกใช้ฟังก์ชั่น  โปรแกรมเมอร์ทราบหรือไม่ว่าภาษาการโปรแกรมนั้นการประเมินผลนิพจน์จะยังคงพิจารณาตาม Precedence rule และ Associativity หรือจะต้องพิจารณาการเรียกฟังก์ชั่นร่วมด้วย เช่น ต้องเรียกก่อนค่อยประเมินนิพจน์ตาม Precedence rule และ Associativity
คำตอบ  ถ้าไม่ทราบต้องไปดู Specification ของภาษา  แต่ละภาษาอาจไม่เหมือนกัน  ส่วนใหญ่ภาษามักกำหนดให้เรียกฟังก์ชั่นให้เสร็จก่อน  แล้วค่อยประเมินนิพจน์

2. ถ้าการเรียกฟังก์ชั่นมีการแก้ไขค่าของตัวแปรที่ปรากฎในนิพจน์  เช่น ตัวแปร A หรือ B (สมมติ A หรือ B เป็น Nonlocal variable ที่ฟังก์ชั่นสามารถเข้าถึงได้  ดังนั้นลำดับการเรียกฟังก์ชั่นก่อนและหลังจะมีผลต่อผลลัพธ์หรือไม่ ?
คำตอบ มีแน่นอน  สมมติว่าเรียกฟังก์ชั่นก่อน ดังนั้นค่า A และ B ที่อาจเกิดการแก้ไขจะเป็นค่าหลังจากเรียกฟังก์ชั่น  ถ้าเรียกฟังก์ชั่นภายหลัง (ทำตาม Precedence rule และ Associativity ตามปกติ) ค่า A และ B ในนิพจน์จะเป็นค่าก่อนเรียกฟังก์ชั่น และต้องจำไว้ว่าในบรรทัดถัดไปจะใช้ค่า A และ B จะเป็นค่าใหม่ที่ถูกแก้ไขแล้ว

สาเหตุจาก Functional Side Effect จึงมาจาก  2 กรณี

1. การเรียกฟังก์ชั่นในนิพจน์ที่มีการแก้ไขค่าของตัวแปร Nonlocal variable ในนิพจน์

#include <stdio.h>
int A = 20, B=5;
void main( ){  
    result  = A * B + myfunction( ) + 20;
   ...
}
int myfunction(  ){
      A  = A +10 ;
        ....
     return A; 
}

เมื่อโปรแกรมเมอร์ทั่วไปที่ไม่ทราบปัญหา Functional side effect จะเข้าใจว่าตัวแปร result จะมีค่าเป็น 150
    result (150)  = 20 * 5 + 30  + 20
แต่ความจริงแล้วเป็น 200
    result (200)  = 30 * 5 + 30 + 20    
 
2. การเรียกฟังก์ชั่นที่พารามิเตอร์ทำงานแบบ out mode หรือ inout mode ซึ่งค่าถูกส่งกลับไปยังเออร์กูเมนต์ที่ปรากฎในนิพจน์ เช่น

void main( ){
  int A = 20, B = 5; 
 result  = A * B + myfunction (&A) + 20; 

}
void myfunction(int *p){
      *p = *p + 10;
}
ความเข้าใจจะเหมือนกรณีแรก

วิธีแก้ไข
พยายามหลีกเลี่ยงการเขียนโค้ดในลักษณะนี้ หากไม่แน่ใจว่าภาษาการโปรแกรมจะมีลำดับการประเมินผลอย่างไร เมื่อมีการเรียกฟังก์ชั่นในนิพจน์ ?
อาจจัดเรียงนิพจน์ใหม่ เช่น

result = A * B + 20;
result = result + myfunction( );
หรือ
result = result =+ myfunction(&A);

ซึ่งเมื่อเรียงใหม่ค่า A ที่นำไปคูณกับ B จะมีค่าเท่ากับ 20 ตามความเข้าใจแรกเริ่ม

Referential transparency 

สมมติ
result1 = (myfunction (A) + B) / (myfunction  (A) - C);
temp = myfunction(A);
result2 = (temp + B) / (temp - C);

ถ้าการเรียกฟังก์ชั่นไม่มีการแก้ไขค่าตัวแปรใดๆ  (A , B, C) ที่เกี่ยวข้องกับนิพจน์  คำสั่งบรรทัดแรกและบรรทัดที่สาม น่าจะได้ผลลัพธ์เดียวกัน  เนื่องจากไม่เกิด Functional side effect
เราจะเรียกว่าโปรแกรมมีคุณสมบัติ Referential transparency
แต่ถ้าเกิดเรียกฟังก์ชั่นในบรรทัดแรกมีการแก้ไขค่า A, B หรือ C จากการเรียกฟังก์ชั่นแล้ว  ผลลัพธ์จากคำสั่งในบรรทัดแรก และบรรทัดสุดท้ายจะไม่เท่ากันทันที