程式人雜誌 -- 2014 年 11 月號 (開放公益出版品)

函數指標陣列 (Array of Function Pointers) (作者:研發養成所 Bridan)

函數指標陣列,這是一種 C/C++ 程式語言的高階設計技巧,希望能有較高的執行效能。 我以 Arduino 當作測試平台,比較兩種程式設計技巧,發現與我的認知有些差異。

先看傳統設計方式,用 switch case 執行不同功能:

//
// Author: Bridan
//         http://4rdp.blogspot.com
// Date:   2014/09/27
//
// Brief:  Test switch case
//

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  TCCR1A = 0x00;                // Normal mode, just as a Timer
  TCCR1B &= ~_BV(CS12);         // no prescaling
  TCCR1B &= ~_BV(CS11);      
  TCCR1B |= _BV(CS10);   
}

void loop() {
  byte i;

  TCNT1 = 0;     // reset timer
  for (i=0 ; i<3 ; i++) {
    switch (i) {
      case 0:
        Serial.println("CASE 0");
        break;
      case 1:
        Serial.println("CASE 1");
        break;
      case 2:
        Serial.println("CASE 2");
        break;
    }
  }
  Serial.println(TCNT1);
}

switch case 3 個時,編譯 2410 bytes,執行 6092 ~ 6100 timer clock switch case 4 個時,編譯 2430 bytes,執行 8136 ~ 8146 timer clock switch case 5 個時,編譯 2458 bytes,執行 10185 ~ 10195 timer clock

將上面程式修改成函數指標陣列,以查表方式直接跳到執行的程式:

//
// Author: Bridan
//         http://4rdp.blogspot.com
// Date:   2014/09/27
//
// Brief:  Test Array of Function Pointers
//

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  TCCR1A = 0x00;                // Normal mode, just as a Timer
  TCCR1B &= ~_BV(CS12);         // no prescaling
  TCCR1B &= ~_BV(CS11);      
  TCCR1B |= _BV(CS10);   
}

void FUNC0(void) {
     Serial.println("CASE 0");
}
void FUNC1(void) {
     Serial.println("CASE 1");
}
void FUNC2(void) {
     Serial.println("CASE 2");
}

void (*TABLE_JUMP[])(void) = {
  FUNC0,
  FUNC1,
  FUNC2
};

void loop() {
  byte i;

  TCNT1 = 0;     // reset timer
  for (i=0 ; i<3 ; i++) {
    (*TABLE_JUMP[i])();
  }
  Serial.println(TCNT1);
}

TABLE 3 個時,編譯 2438 bytes,執行 6082 ~ 6096 timer clock TABLE 4 個時,編譯 2470 bytes,執行 8130 ~ 8142 timer clock TABLE 5 個時,編譯 2504 bytes,執行 10176 ~ 10194 timer clock

以往我所用過的 compiler,switch case 相當於很多 if ... else ... 的組合,條件一個一個比較,數值越大的條件,花費比較的時間越多,以上面的例子在比較方面所費的時間 = 1 + 2 + 3 + ... + N,而函數指標陣列查表時間約 = 1 x N,比 switch case 有效率,這部分與結果相符 (比較條件太少,不易看出差異)。

至於程式碼大小,發現越多條件狀況,以函數指標陣列方式設計比 switch case 程式碼多?因為不清楚 Arduino compiler 如何設計,無法進一步評論,但直覺 Arduino compiler 缺少這方面最佳化處理。