我知道Lisp和Scheme程序员通常会说除非必要,否则应该避免使用eval
。我已经看到了几种编程语言的相同建议,但我还没有看到反对使用eval
的明确论据列表。我在哪里可以找到使用eval
的潜在问题的帐户?
例如,我知道程序编程中GOTO
的问题(使程序难以理解且难以维护,难以找到安全问题等),但我从未见过针对{{1 }}
有趣的是,针对eval
的相同论点应该对延续有效,但我看到Schemers不会说延续是“邪恶的” - 使用它们时应该小心。他们更倾向于使用GOTO
而不是使用延续的代码(据我所知 - 我可能错了)。
答案 0 :(得分:146)
为什么不应该使用EVAL
有几个原因。
初学者的主要原因是:你不需要它。
示例(假设Common Lisp):
使用不同的运算符评估表达式:
(let ((ops '(+ *)))
(dolist (op ops)
(print (eval (list op 1 2 3)))))
那写得更好:
(let ((ops '(+ *)))
(dolist (op ops)
(print (funcall op 1 2 3))))
有很多例子,初学者学习Lisp认为他们需要EVAL
,但他们不需要它 - 因为表达式被评估,人们也可以评估函数部分。大多数情况下EVAL
的使用表明对评估者缺乏了解。
宏是同样的问题。通常初学者会编写宏,他们应该编写函数 - 不了解宏是什么,而不是理解函数已经完成了这项工作。
这通常是使用EVAL
作业的错误工具,它通常表明初学者不理解通常的Lisp评估规则。
如果您认为自己需要EVAL
,请检查是否可以使用FUNCALL
,REDUCE
或APPLY
之类的内容。
FUNCALL
- 调用带参数的函数:(funcall '+ 1 2 3)
REDUCE
- 在值列表上调用函数并合并结果:(reduce '+ '(1 2 3))
APPLY
- 调用一个以列表作为参数的函数:(apply '+ '(1 2 3))
。问:我真的需要eval还是编译器/评估器已经是我真正想要的了?
避免EVAL
略高级用户的主要原因:
你想确保你的代码被编译,因为编译器可以检查代码中的许多问题并生成更快的代码,有时候很多(那是因子1000 ;-))更快的代码
< / LI>构建且需要评估的代码无法尽早编译。
eval任意用户输入会打开安全问题
使用EVAL
进行评估可能会在错误的时间发生并造成构建问题
用简化的例子解释最后一点:
(defmacro foo (a b)
(list (if (eql a 3) 'sin 'cos) b))
所以,我可能想要编写一个基于第一个参数的宏使用SIN
或COS
。
(foo 3 4)
(sin 4)
和(foo 1 4)
执行(cos 4)
。
现在我们可能有:
(foo (+ 2 1) 4)
这不会产生预期的结果。
然后可能想要通过评估变量来修复宏FOO
:
(defmacro foo (a b)
(list (if (eql (eval a) 3) 'sin 'cos) b))
(foo (+ 2 1) 4)
但是这仍然不起作用:
(defun bar (a b)
(foo a b))
在编译时不知道变量的值。
避免EVAL
的一般重要原因:它通常用于丑陋的黑客攻击。
答案 1 :(得分:41)
eval
(用任何语言)都不像电锯那样邪恶。这是一个工具。它恰好是一个强大的工具,当被误用时,可以切断肢体和剔骨(隐喻地说),但程序员工具箱中的许多工具也可以这样说,包括:
goto
和朋友如果你发现自己不得不使用任何这些功能强大的潜在危险工具,请问自己三次“为什么?”在一个链。例如:
“为什么我必须使用
eval
?” “因为foo。” “为什么是foo 必要吗?“”因为...“
如果你到达该链的末尾并且该工具看起来仍然是正确的事情,那就去做吧。将地狱记录下来。测试地狱。一遍又一遍地仔细检查正确性和安全性。但是这样做。
答案 2 :(得分:26)
Eval很好,只要你知道确实它会发生什么。必须检查和验证进入它的任何用户输入以及所有内容。如果您不知道如何100%确定,那就不要这样做。
基本上,用户可以输入相关语言的任何代码,并且它将执行。你可以想象自己可以做多少伤害。
答案 3 :(得分:21)
“我应该何时使用eval
?”可能是一个更好的问题。
简短的回答是“当你的程序打算在运行时编写另一个程序,然后运行它”。 Genetic programming是使用eval
可能有意义的情况示例。
答案 4 :(得分:14)
IMO,此问题并非针对LISP 。以下是针对PHP的相同问题的答案,它适用于LISP,Ruby和其他具有eval的语言:
eval()的主要问题是:
- 潜在的不安全输入。传递不受信任的参数是一种方法 失败。这通常不是一项微不足道的任务 确保参数(或部分 它是完全可信的。
- 棘手。使用eval()会使代码变得更聪明,因此更加困难 跟随。引用Brian Kernighan的话 “调试的难度是原来的两倍 首先编写代码。 因此,如果您将代码编写为 尽可能巧妙地,你是 定义,不够聪明,无法调试 它“
实际使用中的主要问题 eval()只有一个:
- 缺乏经验的开发人员在没有充分考虑的情况下使用它。
取自here。
我认为棘手的一点是令人惊讶的。对代码高尔夫和简洁代码的痴迷总是产生“聪明”代码(对于这些代码来说,eval是一个很好的工具)。但是你应该编写你的代码以便于阅读,IMO,而不是为了证明你是一个聪明而且不是为了节省纸张(你无论如何都不打印它)。
然后在LISP中存在与运行eval的上下文相关的一些问题,因此不受信任的代码可以访问更多内容;无论如何,这个问题似乎很常见。
答案 5 :(得分:12)
有很多很棒的答案,但这是另一个来自Racket的实施者Matthew Flatt:
http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html
他提出了许多已经涵盖的观点,但有些人可能会发现他的观点很有趣。
摘要:使用它的上下文会影响eval的结果,但程序员通常不会考虑它,从而导致意外的结果。
答案 6 :(得分:11)
规范的答案是远离。我觉得很奇怪,因为它是一个原始的,七个原始的(其他的是缺点,汽车,cdr,if,eq和引用),它远远地消耗了最少的使用和爱。
来自 On Lisp :“通常,明确地调用eval就像在机场礼品店买东西一样。等到最后一刻,你必须为有限的第二个选择支付高价 - 货物。“
那么我什么时候使用eval?一个正常的用法是通过评估(loop (print (eval (read))))
在REPL中使用REPL。每个人都可以使用。
但您也可以通过将eval与backquote相结合,根据将在编译之后评估的宏来定义函数。你去了
(eval `(macro ,arg0 ,arg1 ,arg2))))
它将为你杀死上下文。
Swank(对于emacs史莱姆)充满了这些案例。它们看起来像这样:
(defun toggle-trace-aux (fspec &rest args)
(cond ((member fspec (eval '(trace)) :test #'equal)
(eval `(untrace ,fspec))
(format nil "~S is now untraced." fspec))
(t
(eval `(trace ,@(if args `(:encapsulate nil) (list)) ,fspec ,@args))
(format nil "~S is now traced." fspec))))
我认为这不是一个肮脏的黑客。我自己一直用它来将宏重新集成到函数中。
答案 7 :(得分:7)
关于Lisp eval的另外几点:
答案 8 :(得分:4)
就像GOTO“规则”:如果你不知道自己在做什么,你就会弄得一团糟。
除了仅使用已知和安全的数据构建内容之外,还存在一些语言/实现无法充分优化代码的问题。您最终可能会在eval
内找到解释后的代码。
答案 9 :(得分:4)
Eval只是不安全。 例如,您有以下代码:
eval('
hello('.$_GET['user'].');
');
现在用户访问您的网站并输入网址http://example.com/file.php?user=); $ is_admin = true; echo(
然后生成的代码将是:
hello();$is_admin=true;echo();
答案 10 :(得分:2)
Eval不是邪恶的。 Eval并不复杂。它是一个函数,用于编译传递给它的列表。在大多数其他语言中,编译任意代码意味着学习语言的AST并在编译器内部挖掘以找出编译器API。在lisp中,您只需调用eval。
什么时候应该使用它?每当您需要编译某些东西时,通常是一个在运行时接受,生成或修改任意代码的程序。
什么时候不应该使用它?所有其他情况。
为什么不在不需要的时候使用它?因为你会以不必要的复杂方式做某事,这可能会导致可读性,性能和调试方面的问题。
是的,但如果我是初学者,我怎么知道我是否应该使用它?始终尝试使用功能实现您需要的功能。如果这不起作用,请添加宏。如果仍然无效,那么eval!
遵循这些规则,你永远不会用eval做恶:)
答案 11 :(得分:0)
我非常喜欢Zak's answer并且他已经了解了问题的本质:当您编写新语言,脚本或语言修改时,会使用 eval 。他没有进一步解释,所以我举一个例子:
(eval (read-line))
在这个简单的Lisp程序中,系统会提示用户输入,然后评估他们输入的内容。为了实现这一点,如果编译程序,则必须存在整个符号定义集,因为您不知道用户可能输入哪些函数,因此您必须将它们全部包含在内。这意味着如果你编译这个简单的程序,结果二进制文件将是巨大的。
原则上,由于这个原因,你甚至不能认为这是一个可编辑的陈述。通常,一旦使用 eval ,就会在解释环境中运行,并且无法再编译代码。如果你不使用 eval 那么就可以像C程序一样编译Lisp或Scheme程序。因此,在提交使用 eval 之前,您需要确保需要并且需要处于解释的环境中。