2010年9月5日 星期日

Makefile-把多個程式連結在一起的寫法

執行make的時候總會去找一個叫做Makefile的東西。
========
Makefile範例
====
CC = gcc
CFLAGS =
INCLUDE_PATH = .
SRCS = red.c blue.c
OBJS = $(SRCS:.c=.o)
PROGRAM = exe
all:
<TAB>make $(PROGRAM)
$(PROGRAM):$(OBJS)
<TAB>$(CC) $(OBJS) -o $@
%.o: %.c
<TAB>$(CC) $(CFLAGS) -I$(INCLUDE_PATH) -c $< -o $@
clean:
<TAB>rm -rf *.o
<TAB>rm -rf $(PROGRAM)
========
在執行make時會有這樣子的輸出:
make exe

make[1]: Entering directory `/usr/src/local/test/fp'
gcc -I. -c red.c -o red.o
gcc -I. -c blue.c -o blue.o
gcc red.o blue.o -o exe
make[1]: Leaving directory `/usr/src/local/test/fp'
 
在執行make clean時會有這樣子的輸出:
rm -rf *.o
rm -rf exe

函式指標(function pointer)-用途

我喜歡用"寫程式的先後順序"來理解 函式指標。

假設兩位程式設計師Red,Blue同時合作撰寫一個"顯示時間"的專案。
Red負責取得系統時間,Blue負責顯示(電子鐘,復古鐘,給寵物看的鐘,...whatever)。
這時候程式的寫法大約是這樣
========
Project : SHOW CLOCK IN VARIOUS TYPE
====
//blue.c written by blue
void blue_show_clock_digitial(time_t t){
  printf("Now:%02d:%02d:%02d\n",t.hour,t.min,t.second);
}
void blue_show_clock_1(time_t t){/*Not implemented yet*/}
void blue_show_clock_2(time_t t){/*Not implemented yet*/}

void blue_show_clock_3(time_t t){/*Not implemented yet*/}
//...//
====
//main.c written by red
time_t red_get_clock(){
  /*
   *Do something to get time
   */
  return time;

int main(){
  /* ... */
  time = red_get_clock();
  blue_show_clock_digital(time);// This function is implemented by programmer 'blue'
  /* ... */
}
========
這時候注意程式碼撰寫順序的問題。
理論上來說red所撰寫的程式未來幾乎是不太需要再做更改了,而blue所撰寫的程式卻有可能因為使用者的需求而有所變動。
當blue變動的時候,red就需要把blue_show_clock_digital(time)這一行改掉,改成blue所撰寫的函式(blue_show_clock_1()...等等)。

如果我是red,改個一兩次還沒關係,天天都來改的話,我一定會抓狂。(使用者一定會叫blue改,blue就叫我來改...)
每次blue改程式,我就要跟著改,但是事實上我red寫的程式核心幾乎沒有動耶。

這時候就是函式指標派上用途的時機了。看以下範例的改寫
========
Project : SHOW CLOCK IN VARIOUS TYPE by FUNCTION POINTER
====
//blue.c written by blue
void blue_show_clock_digitial(time_t t){
printf("Now:%02d:%02d:%02d\n",t.hour,t.min,t.second);
}
void blue_show_clock_1(time_t t){/*Not implemented yet*/}
void blue_show_clock_2(time_t t){/*Not implemented yet*/}
void blue_show_clock_3(time_t t){/*Not implemented yet*/}
//...//
red_display = blue_show_clock_digitial;
====
//main.c written by red
void (*red_display)(time_t);//red_display is a function pointer
time_t red_get_clock(){
/*
*Do something to get clock
*/
return time;
}
int main(){
/* ... */
time = red_get_clock();
red_display(time);// red_display is a function pointer
/* ... */
}
========
 red先宣告一個函式指標red_display(),然後請blue把這個函式指標red_display()納進blue.c裡面。
每次blue收到使用者需求需要新增一個新的函式時,就把這個新的函式assign給red宣告的函式指標red_display。
 
函式指標需要三個部份,說穿了他只是一個長的比較怪的參數型態:
void (*red_display) (time_t);
回傳值     函式指標名稱          函式參數型態
 
函數指標的assign是這樣:
red_display = blue_show_clock_digitial;
簡單來說就是把return type跟參數type跟括號通通拿掉,然後再用`=`連在一起就好囉。
比較標準的寫法是這樣  :
red_display = &blue_show_clock_digitial;
兩者皆可。


如此一來當使用者修改需求時,只需要blue更改blue.c裡面就可以囉。
對red來說,我以後都不需要再改跟我無關的程式。
對blue來說,比較不需要麻煩別人。
對其他人而言,程式的可讀性(red.c裡面的red_display()是固定的)和彈性(blue修改時不需要跟red同步)變的好多了。
 
這樣子的特性讓函式指標常常會用在做所謂callback function : 先實作的函式(red_display)"call back"後實作的函式(blue_show_clock_xxx)。
由於函數指標可以像是變數一樣,可以在執行時期才做assign的動作,所以就稱為late-binding,late到執行時期,才binding指定的函式。
在大型專案中就會常常發現,像是Linux核心內,隨便撿都有。