函數功能:函數“eval”是一個比較特殊的函數。使用它可以在Makefile中構造一個可變的規則結構關系(依賴關系鏈),其中可以使用其它變量和函數。函數“eval”對它的參數進行展開,展開的結果作為Makefile的一部分,make可以對展開內容進行語法解析。展開的結果可以包含一個新變量、目標、隱含規則或者是明確規則等。也就是說此函數的功能主要是:根據其參數的關系、結構,對它們進行替換展開。
返回值:函數“eval”的返回值時空,也可以說沒有返回值。
函數說明:“eval”函數執行時會對它的參數進行兩次展開。第一次展開過程發是由函數本身完成的,第二次是函數展開后的結果被作為Makefile內容時由make解析時展開的。明確這一過程對於使用“eval”函數非常重要。理解了函數“eval”二次展開的過程后。實際使用時,如果在函數的展開結果中存在引用(格式為:$(x)),那么在函數的參數中應該使用“$$”來代替“$”。因為這一點,所以通常它的參數中會使用函數“value”來取一個變量的文本值。
其實 eval 在函數式語言里面很常見。LISP 系語言的解釋器,最終執行的是一個 apply - eval 遞歸(有人也喜歡叫 apply - eval 循環,但是實際上是遞歸求值)。所以 eval 就是求值的意思。實際上,不只是 LISP,可以說任意解釋器,最終都是 apply - eval 遞歸。bash 里面也有 eval. 只不過,在 LISP 里面,這種 apply - eval 通過其(語法...)形式,更加顯式地表達出來了,所以 LISP 里面的 apply - eval 也就更著名。
遞歸是容易讓人暈菜的玩意,真正搞懂遞歸的程序員,其實並不是想像的那么多。比如圖靈機,圖靈機可以接受的語言被稱為遞歸可枚舉集。具體的數學上的定義就不扯淡了。其實這句話的意思,想想 C 程序的執行流程就知道了,一個 main 函數,里面調幾個函數,這幾個函數執行完了,程序就執行完了。那這個幾個函數實際上就可以看着為一個可枚舉的集合的成員。而每一個函數,都可以寫成遞歸的形式,原因很簡單,因為任意判斷和循環都可以寫成遞歸的形式。
扯遠了,說回來。關於 Makefile 里面的 eval, 以及我本帖所舉的那個 Makefile 手冊里面的長篇大論,說什么二次求值什么的,其實並沒有真正說清楚。我想了一個極其簡單的例子來說清楚 Makefile 里面的 eval.
Makefile:
1 ############################################### 2 pointer := pointed_value 3 4 define foo 5 var := 123 6 arg := $1 7 $$($1) := ooooo 8 endef 9 10 $(info $(call foo,pointer)) 11 #$(eval $(call foo,pointer)) 12 13 target: 14 @echo ----------------------------- 15 @echo var: $(var), arg: $(arg) 16 @echo pointer: $(pointer), pointed_value: $(pointed_value) 17 @echo done. 18 @echo ----------------------------- 19 20 ###############################################
注意上面的例子,$(eval $(call foo, pointer)) 那行被注釋了。先執行這個注釋了那行的 Makefile,結果如下:
- var := 123
- arg := pointer
- $(pointer) := ooooo
- -----------------------------
- var: , arg:
- pointer: pointed_value, pointed_value:
- done.
- -----------------------------
注意,
var := 123
arg := pointer
$(pointer) := ooooo
這幾行就是 $(call foo,pointer) 的結果(或者說,調用 foo 這個 "函數"(因為 Makefile 中正式的名字叫做宏包) 的返回值)。同時注意到, var, arg, pointed_value 都是空值,因為我實際上只是通過 $(info ) 函數將替換了參數后的 foo 函數體,或者說 $(call foo, pointer) 的返回值打印到標准輸出而已($1 就是 pointer, 調用函數,就直接替換下參數而已),所以,這幾行代碼並沒有真正執行。
注意了,這個 $(call foo,pointer) 就是 Makefile 對 foo 函數的第一次求值。上面看到了,實際上求值出來的結果還是 Makefile 代碼。
那么問題就來了。既然求值出來的結果還是 Makefile 代碼,那這段代碼又要怎么運行呢?答案就是再包一個 eval, 所以 eval 就是第二次求值了。
因此,如果將 $(eval $(all foo,pointer)) 那行注釋取消掉的話,運行結果如下:
- var := 123
- arg := pointer
- $(pointer) := ooooo
- -----------------------------
- var: 123, arg: pointer
- pointer: pointed_value, pointed_value: ooooo
- done.
- -----------------------------
OK. 注意,var, arg, pointed_value 都被賦值了,這個賦值操作就是第一次求值出來的代碼運行的結果。
那么,為什么在寫 foo 這個宏包的時候,要寫成$$($1) := ooooo呢?
因為 Makefile 里面 $ 是元字符(meta-chara...),也就是它是有特殊意義的。那在 Makefile 里面表示"字符" $ 就得用 $$. 看第一次求值的結果就知道了,不用多說。
參考: http://bbs.chinaunix.net/thread-2321462-1-1.html