eval

維基百科,自由的百科全書
跳至導覽 跳至搜尋

在一些程序語言中,eval 是一個把字符串當作表達式執行而返回一個結果的函數;在另一些之中,它執行多行的代碼就好像它們被包括在其中,而不是包括 eval 的這一行。eval 的輸入不一定是字符串;在支持句法抽象的語言(如 Lisp)中,eval 的輸入將會由抽象句法形式組成。

安全風險[編輯]

當使用 eval 而數據來自不可信任的來源時,一定要特別的注意。比如說,假設 get_data() 函數從 Internet 獲取數據,這個 Python 代碼就是不安全的:

session['authenticated'] = False
data = get_data()
foo = eval(data)

一個攻擊者可以讓字符串 "session.update(authenticated=True)" 作為數據提供給這個程序,它將會更新 session 字典以設定一個通過身份驗證的鍵為 True。為了補救這一點,所有將會被 eval 使用的數據必須被轉義,或者必須運行於無法訪問可能有害的函數的環境下。

使用[編輯]

eval 的調用有時被沒經驗的程式設計師在所有種類的事物上使用。在絕大多數情況下,有更加靈活而不會造成解析代碼時速度損失的可替代的方法。

比如說,eval 有時被用於一個簡單的郵件合併英語Mail merge設施上,就像如下 PHP 代碼所示:

$name = 'John Doe';
$greeting = 'Hello';
$template = '"$greeting,  $name! How can I help you today?"';
print eval("return $template;");

儘管這確實有用,它可能導致一些安全問題 (見安全風險),而且比其它的解決方案慢很多。一個更快和更安全的解決方案可以是改變最後一行為 print $template; 和移除前一行的單引號,或者使用 printf

eval 有時也被用於需要對數學表達式求值的應用中,如電子試算表。這比寫一個表達式解析器簡單多了,但是發現或者寫出一個表達式解析器通常是一個更好的選擇。除了可修復的安全風險,使用這種語言的求值特性通常很有可能更慢,也沒有那麼高的可定製性。

也許 eval 的最佳使用是在 bootstrapping英語Bootstrapping (compilers) 一個新的語言的過程中 (就像 Lisp 那樣),以及作為語言的允許用戶在受控制的環境下運行他們自己的程序的指導程序。

出於表達式求值的目的,eval 相比表達式解析器的主要優勢在於,在 eval 受支持的絕大多數編程環境下,這個表達式可能是任意的複雜,而且可能包括對使用者所寫的不可能被解析器的創造者所預先知道的函數的調用。這個能力允許你用一個你可以按需要增強的函數庫有效的增加 eval() 引擎,而不必持續的維持一個表達式解析器。然而,如果你不需要這麼終極的靈活性,表達式解析器遠遠更加有效和輕量。

實施[編輯]

直譯語言中,eval 幾乎總是被和正常的代碼使用一樣的解析器實現。在編譯語言中,用於編譯程序的編譯器可能被嵌入在使用 eval 的程序中; 分開來的解釋器有時也被使用,儘管這可能會導致重複代碼

程序語言[編輯]

JavaScript[編輯]

JavaScript 中,eval 是某種介於表達式求值器和語句執行器的混合體。它返回最後一個被求值的表達式的值 (在 JavaScript 中,所有語句都是表達式),也允許最後一個分號省略。

如下示例是一個表達式求值器:

foo = 2;
alert(eval('foo + 2'));

如下示例則是一個語句執行器。

foo = 2;
eval('foo = foo + 2;alert(foo);');

JavaScript 的 eval 的一個應用是解析 JSON 文本,也許是作為 Ajax 框架中的一部分。然而,現代的瀏覽器提供 JSON.parse 作為這個任務的一個更加安全的替代品。

ActionScript[編輯]

ActionScript (Flash 的程式語言) 中,eval 不能用於計算任意的表達式。根據 Flash 8 文檔,它的使用僅限於代表「變量名,屬性,對象或者要檢索的影片剪輯。這個參數可以是一個字符串或者對對象實例的一個直接引用」的表達式。[1]

ActionScript 3 不支持 eval。

ActionScript 3 Eval Library[2] 和 D.eval API[3] 是進行中的用以在 ActionScript 3 中創建 eval 的等價物的開發項目。

Lisp[編輯]

Lisp 是首先使用 eval 函數的語言。事實上,對 eval 函數的定義導致了該語言解釋器的最初的實施。[4]eval 函數被定義之前,Lisp 函數只是手動被編譯成匯編語言語句。然而,一旦 eval 函數被手動編譯,它隨後就被用於組成第一個 Lisp 解釋器的基礎的Read–eval–print循環的一部分。

Lisp eval 函數的後來版本也被作為編譯器實施。

Lisp 中的 eval 函數期望一個形式作為一個參數被求值和執行。給定形式的返回值將會是對 eval 的調用的返回值。

這是一個示例 Lisp 代碼:

; A form which calls the + function with 1,2 and 3 as arguments.
; It returns 6.
(+ 1 2 3)
; In lisp any form is meant to be evaluated, therefore
; the call to + was performed.
; We can prevent Lisp from performing evaluation
; of a form by prefixing it with "'", for example:
(setq form1 '(+ 1 2 3))
; Now form1 contains a form that can be used by eval, for
; example:
(eval form1)
; eval evaluated (+ 1 2 3) and returned 6.

Lisp 眾所周知的非常靈活,eval 函數也是。例如,為了對字符串的內容求值,這個字符串首先必須使用 read-from-string 函數轉化為 Lisp 格式,隨後這個結果的格式將會被傳給 eval:

(eval (read-from-string "(format t \"Hello World!!!~%\")"))

主要造成混淆的一點是這個問題,即在哪個上下文中這個形式中的符號會被求值。在上述示例中,form1 包含符號 +。對該符號的求值必然會產生一個用於加法的函數以使得該示例像預期那樣工作。因而 Lisp 的某些方言允許為 eval 傳入一個額外的參數以指定求值的上下文 (類似於 Python 的 eval 函數的可選參數 - 如下所示)。一個用 Lisp 的 Scheme 方言 (R5RS 和以後版本) 寫出的示例:

;; Define some simple form as in the above example.
(define form2 '(+ 5 2))
;Value: form2
 
;; Evaluate the form within the initial context.
;; A context for evaluation is called an "environment" in Scheme slang.
(eval form2 user-initial-environment)
;Value: 7
 
;; Confuse the initial environment, so that + will be
;; a name for the subtraction function.
(environment-define user-initial-environment '+ -)
;Value: +
 
;; Evaluate the form again.
;; Notice that the returned value has changed.
(eval form2 user-initial-environment)
;Value: 3

Perl[編輯]

Perl 中,eval 函數是某種介於表達式求值器和語句執行器的混合體。它返回最後一個被求值的表達式的結果 (在 Perl 編程中,所有的語句都是表達式),且允許最後一個分號省略。

一個表達式求值器的示例:

$foo = 2;
print eval('$foo + 2'), "\n";

一個語句執行器的示例:

$foo = 2;
eval('$foo += 2; print "$foo\n";');

(注意字符串的引號。注意單引號在上述示例中被用來引用字符串。如果使用的是雙引號,它將會在把該字符串傳入 eval 之前把變量的值插入字符串,破壞了 eval 原本的目的,而且在賦值的情況下,有可能引起句法錯誤。)

Perl 也有 eval ,作為它的異常處理機制。這與上述對 eval 傳入字符串的用法不同,在於 eval 內的代碼在編譯時而不是運行時解釋,所以它不是本文中使用的 eval 的含義。

PHP[編輯]

PHP 中,eval 執行在一個字符串中的代碼幾乎就像它被放進了文件中,而不是對 eval() 的調用一樣。唯一的區別是錯誤被報道為來自對 eval() 的一個調用,而返回語句則成為函數的結果。

參考來源[編輯]