왜 평가가 정확히 악한가?


141

나는 Lisp와 Scheme 프로그래머들이 일반적으로 eval꼭 필요한 경우가 아니라면 피해야 한다고 말합니다 . 여러 프로그래밍 언어에 대해 동일한 권장 사항을 보았지만에 대한 사용에 대한 명확한 주장 목록을 아직 보지 못했습니다 eval. 사용의 잠재적 문제에 대한 설명은 어디서 찾을 수 있습니까 eval?

예를 들어, GOTO절차 적 프로그래밍 의 문제 (프로그램을 읽을 수없고 유지하기 어렵게 만들고 보안 문제를 찾기 어렵게 만드는 등)를 알고 있지만에 대한 주장을 본 적이 없습니다 eval.

흥미롭게도, 같은 주장에 GOTO대해서는 계속해서 반대 해야하지만 Schemers는 예를 들어, 계속이 "악"이라고 말하지 않을 것입니다. 사용시주의해야합니다. eval연속체를 사용하는 코드보다 코드를 사용하여 눈살을 찌푸 릴 가능성이 훨씬 높습니다 (내가 볼 수있는 한 잘못 될 수 있습니다).


5
eval은 악이 아니지만 악은 eval이하는 일입니다
Anurag

9
@ yar-귀하의 의견은 매우 단일 한 객체 중심의 세계관을 나타냅니다. 아마도 대부분의 언어에서 유효하지만 메소드가 클래스에 속하지 않고 Clojure에서는 훨씬 더 다른 Common Lisp에서는 다를 수 있습니다. 여기서 Clojure에서는 Java interop 함수를 통해서만 클래스가 지원됩니다. Jay는이 질문에 Scheme이라고 태그를 붙 였는데, 클래스 나 메소드에 대한 기본 개념이 없습니다 (다양한 OO 형식은 라이브러리로 사용 가능).
Zak

3
@ Zak, 당신은 맞습니다. 나는 내가 알고있는 언어 만 알고 있지만 스타일을 사용하지 않고 Word 문서로 작업하더라도 건조하지 않습니다. 내 요점은 자신을 반복하지 않기 위해 기술을 사용하는 것이 었습니다. OO는 보편적이지 않습니다 ...
Dan Rosenstark

4
Clojure 사용자가 여기에 게시 된 탁월한 답변에 노출되면 이점을 얻을 수 있다고 생각하기 때문에 clojure 태그를이 질문에 자유롭게 추가했습니다.
Michał Marczyk

Clojure의 경우 적어도 하나 이상의 추가 이유가 적용됩니다. ClojureScript 및 그 파생물과의 호환성이 손실됩니다.
Charles Duffy

답변:


148

사용하지 말아야 할 몇 가지 이유가 있습니다 EVAL.

초보자의 주된 이유는 필요하지 않기 때문입니다.

예 (공통 리스프 가정) :

다른 연산자를 사용하여 표현식을 평가하십시오.

(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)).

Q : 실제로 eval이 필요합니까, 아니면 컴파일러 / 평가자가 실제로 원하는 것을 가지고 있습니까?

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: 추악한 해킹에 자주 사용됩니다.


3
감사! 방금 마지막 요점을 이해하지 못했습니다 (잘못된 시간에 평가?)-좀 더 정교하게 부탁드립니다.
Jay

41
이것이 진정한 해답이므로 +1입니다. 사람들 eval은 자신이하고 싶은 일을 할 수있는 특정 언어 나 라이브러리 기능이 있다는 것을 모르기 때문에 다시 돌아옵니다 . JS의 비슷한 예 : 동적 이름을 사용하여 객체에서 속성을 얻고 싶습니다. 그래서 쓸 eval("obj.+" + propName)수 있습니다 : obj[propName].
Daniel Earwicker

무슨 말인지 알 겠어요, 라이너! 감사합니다!
Jay

@ 다니엘 : "obj.+"? 마지막으로 확인 +했는데 JS에서 도트 참조를 사용할 때 유효하지 않습니다.
Hello71

2
@Daniel은 아마도 예상대로 작동해야하는 eval ( "obj."+ propName)을 의미했을 것입니다.
claj

41

eval(어떤 언어로든) 전기 톱이 악한 것과 같은 방식으로 악한 것은 아닙니다. 도구입니다. 잘못 사용하면 팔다리를 절단하고 (비 유적으로 말하면) 볼 수있는 강력한 도구이지만, 프로그래머의 도구 상자에있는 많은 도구에 대해서도 다음과 같이 말할 수 있습니다.

  • goto 그리고 친구들
  • 잠금 기반 스레딩
  • 연속
  • 매크로 (유전자 또는 기타)
  • 포인터
  • 재시작 가능한 예외
  • 자체 수정 코드
  • ... 그리고 수천의 캐스트.

이러한 강력하고 잠재적으로 위험한 도구를 사용해야하는 경우 "왜?" 사슬에서. 예를 들면 다음과 같습니다.

"왜 사용해야합니까 eval 합니까?" "foo 때문에." "왜 foo가 필요한가?" "때문에 ..."

해당 체인의 끝에 도달했지만 도구가 여전히 옳은 것처럼 보이는 경우 수행하십시오. 지옥을 문서화하십시오. 지옥을 시험해보십시오. 반복해서 정확성과 보안을 다시 확인하십시오. 하지만하세요.


고마워. 그것은 내가 전에 eval에 대해 들었던 것 ( "이유를 묻는다")이지만 잠재적 인 문제가 무엇인지 들어 보거나 읽지 못했습니다. 이제 답변에서 보안 상태 및 성능 문제가 무엇인지 알 수 있습니다.
Jay

8
코드 가독성. Eval은 코드 흐름을 완전히 망쳐 서 이해할 수 없게 만듭니다.
저의 정확한 의견 그냥

"lock-based threading"[sic]이 (가) 귀하의 목록에있는 이유를 모르겠습니다. 잠금을 포함하지 않는 동시성 형식이 있으며 잠금 관련 문제는 일반적으로 잘 알려져 있지만 잠금을 "악"으로 사용한다고 말하는 사람은 없습니다.
asveikau

4
asveikau : 잠금 기반 스레딩은 제대로 이해하기 어려운 것으로 악명이 높습니다 (잠금을 사용하는 프로덕션 코드의 99.44 %가 나쁘다고 생각합니다). 작성하지 않습니다. "멀티 스레드"코드를 직렬 코드로 변환하기 쉽습니다. (이를 수정하면 코드가 느려지고 부풀어 오릅니다.) STM 또는 액터 모델과 같은 잠금 기반 스레딩에 대한 대안은 아주 좋지 않습니다.
나의 올바른 의견

"왜 체인":) 3 단계 후에 중단해야합니다.
szymanowski

27

정확히 아는 한 평가는 괜찮습니다.무슨 일이 일어나고 있다면 습니다. 여기에 들어가는 모든 사용자 입력을 확인하고 확인해야합니다. 100 % 확신하는 방법을 모른다면 그렇게하지 마십시오.

기본적으로 사용자는 해당 언어의 코드를 입력 할 수 있으며 실행됩니다. 그가 할 수있는 피해량을 스스로 상상할 수 있습니다.


1
따라서 실제로 사용자 입력을 직접 복사하지 않는 알고리즘을 사용하여 사용자 입력을 기반으로 S- 표현을 생성 하는 경우 매크로 나 다른 기술을 사용하는 것보다 특정 상황에서 더 쉽고 명확하다면 "사악한 것이 없다"고 가정합니다. "그것에 대해? 즉, eval의 유일한 문제는 SQL 쿼리 및 사용자 입력을 직접 사용하는 다른 기술과 동일합니까?
Jay

10
그것이 "악"이라고 불리는 이유는 잘못하는 것이 다른 일을 잘못하는 것보다 훨씬 나쁘기 때문입니다. 그리고 우리가 알고 있듯이, 초보자는 잘못된 일을 할 것입니다.
Tor Valamo

3
모든 상황에서 코드를 회피하기 전에 코드를 확인해야한다고 말하지는 않습니다. 예를 들어 간단한 REPL을 구현할 때 입력을 선택하지 않은 채 eval에 입력하면 문제가되지 않습니다 (물론 웹 기반 REPL을 작성할 때 샌드 박스가 필요하지만 일반적인 경우는 아닙니다) 사용자 시스템에서 실행되는 CLI-REPL).
sepp2k

1
내가 말했듯이, 당신은 당신이 평가 한 음식을 평가할 때 무슨 일이 일어나는지 정확히 알아야합니다. "샌드 박스의 한계 내에서 일부 명령을 실행합니다"를 의미한다면 이것이 의미하는 것입니다. ;)
Tor Valamo

@TorValamo는 탈옥 소식을 들어 본 적이 있습니까?
Loïc Faure-Lacroix

21

"언제 사용해야 eval합니까?" 더 좋은 질문이 될 수 있습니다.

짧은 대답은 "프로그램이 런타임에 다른 프로그램을 작성하고 실행하려고 할 때"입니다. 유전자 프로그래밍 은 사용하기에 적합한 상황의 예입니다 eval.


14

IMO, 이 질문은 LISP에만 국한되지 않습니다 . 다음은 PHP와 동일한 질문에 대한 답변이며 LISP, Ruby 및 기타 평가 기능이있는 다른 언어에 적용됩니다.

eval ()의 주요 문제점은 다음과 같습니다.

  • 잠재적으로 안전하지 않은 입력. 신뢰할 수없는 매개 변수를 전달하면 실패 할 수 있습니다. 매개 변수 (또는 그 일부)를 완전히 신뢰하는 것은 쉬운 일이 아닙니다.
  • 어리 석음. eval ()을 사용하면 코드가 영리 해지기 때문에 따르기가 더 어렵습니다. Brian Kernighan의 말을 인용하자면, " 디버깅은 처음에 코드를 작성하는 것보다 두 배나 어렵습니다. 따라서 가능한 한 영리하게 코드를 작성한다면, 정의에 따르면 디버그하기에 충분히 똑똑하지 않습니다 "

실제로 eval ()을 사용할 때의 주요 문제는 하나뿐입니다.

  • 충분히 고려하지 않고 사용하는 경험이없는 개발자.

여기 에서 찍은 .

나는 까다로운 부분이 놀라운 포인트라고 생각합니다. 코드 골프와 간결한 코드에 대한 집착은 항상 "영리한"코드를 만들어 냈습니다 (에바는 훌륭한 도구입니다). 그러나 가독성을 높이고 종이를 절약하지 않음을 나타 내기 위해 가독성을위한 코드를 작성해야합니다. (당신이 어쨌든 인쇄되지 않습니다).

그런 다음 LISP에는 eval이 실행되는 컨텍스트와 관련된 몇 가지 문제가 있으므로 신뢰할 수없는 코드는 더 많은 것에 액세스 할 수 있습니다. 이 문제는 어쨌든 흔한 것 같습니다.


3
EVAL의 "악한 입력"문제는 리스프가 아닌 언어에만 영향을 미칩니다. 이러한 언어에서는 eval ()이 일반적으로 문자열 인수를 취하고 사용자의 입력이 스 플라이 싱되기 때문입니다. 사용자는 입력에 따옴표를 포함하고 이스케이프 할 수 있습니다. 생성 된 코드 그러나 Lisp에서 EVAL의 인수는 문자열이 아니며 절대적으로 무모한 경우가 아니라면 사용자 입력이 코드로 이스케이프 될 수 없습니다 (들어가면 READ-FROM-STRING으로 입력을 구문 분석하여 S 표현식을 작성하는 것처럼) 인용 부호가없는 EVAL 코드를 인용하면 인용 부호를 이스케이프 할 방법이 없습니다.
버리기 계정

12

많은 훌륭한 답변이 있었지만 여기에 라켓의 구현 자 중 하나 인 Matthew Flatt가 가져 왔습니다.

http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html

그는 이미 다룬 많은 요점을 제시하지만 일부 사람들은 그럼에도 불구하고 그의 관심을 끌 수 있습니다.

요약 : 사용 된 컨텍스트는 평가 결과에 영향을 주지만 종종 프로그래머가 고려하지 않아 예기치 않은 결과가 발생합니다.


11

정식 답변은 멀리하는 것입니다. 나는 그것이 원시적이기 때문에 이상한 것을 발견하고, 7 가지 기본 요소 (다른 것들은 죄수, 자동차, cdr, if, eq 및 quote) 중 가장 적은 양의 사용과 사랑을 얻습니다.

에서 에 리스프 ". 일반적으로, 명시 적으로 평가를 호출하면 공항 선물 가게에서 뭔가를 구입처럼 마지막 순간까지 기다렸다 데, 당신은 이류 제품의 제한된 선택을 위해 높은 가격을 지불해야한다"고 말했다.

eval은 언제 사용합니까? 일반적인 사용 중 하나는를 평가하여 REPL 내에 REPL을 갖는 것 (loop (print (eval (read))))입니다. 그 사용에는 누구나 괜찮습니다.

그러나 eval과 backquote를 결합하여 컴파일 평가 될 매크로로 함수를 정의 할 수도 있습니다 . 너가

(eval `(macro ,arg0 ,arg1 ,arg2))))

그리고 그것은 당신을 위해 문맥을 죽일 것입니다.

스 nk 크 (이맥스 점액의 경우)에는 이러한 경우가 가득합니다. 그들은 다음과 같이 보입니다 :

(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))))

나는 그것이 더러운 해킹이라고 생각하지 않습니다. 매크로를 함수로 다시 통합하기 위해 항상 사용합니다.


1
커널 언어를 확인하고 싶을 수도있다;)
artemonster

7

Lisp eval에 대한 또 다른 몇 가지 사항 :

  • 글로벌 환경에서 로컬 컨텍스트를 잃어 평가합니다.
  • 읽기-매크로 '#'을 실제로 사용하려고 할 때 eval을 사용하고 싶을 때가 있습니다. 읽기시 평가됩니다.

나는 글로벌 환경의 사용이 Common Lisp과 Scheme 모두에 해당된다는 것을 이해합니다. Clojure도 마찬가지입니까?
Jay

2
구성표 (적어도 R7RS, 아마도 R6RS의 경우)에서 환경을 평가해야합니다.
csl

4

GOTO "규칙"처럼 : 당신이 무엇을하고 있는지 모른다면, 엉망이 될 수 있습니다.

알려진 안전한 데이터로 무언가를 구축하는 것 외에도 일부 언어 / 구현으로 코드를 충분히 최적화 할 수 없다는 문제가 있습니다. 내부에서 해석 된 코드로 끝날 수 있습니다 eval.


그 규칙은 GOTO와 어떤 관련이 있습니까? 엉망으로 만들 수없는 프로그래밍 언어 기능이 있습니까?
Ken

2
@ Ken : GOTO 규칙이 없으므로 대답에 따옴표가 있습니다. 스스로 생각하기를 두려워하는 사람들에게는 교리가 있습니다. 평가도 동일합니다. eval을 사용하여 Perl 스크립트의 속도를 극적으로 향상시키는 것을 기억합니다. 도구 상자에있는 하나의 도구입니다. 초보자는 다른 언어 구성이 더 쉬울 때 더 자주 eval을 사용합니다. 그러나 단지 시원하고 독단적 인 사람들을 기쁘게하기 위해 완전히 피하는가?
stesch

4

평가는 안전하지 않습니다. 예를 들어 다음 코드가 있습니다.

eval('
hello('.$_GET['user'].');
');

이제 사용자가 귀하의 사이트를 방문하여 url http://example.com/file.php?user=를 입력하십시오 . ); $ is_admin = true; echo (

결과 코드는 다음과 같습니다.

hello();$is_admin=true;echo();

6
그는 Lisp가 PHP가 아니라고 생각했다
fmsf

4
@fmsf 그는 Lisp에 대해 구체적으로 말했지만 일반적으로 Lisp를 eval사용하는 모든 언어 에 대해 이야기 했습니다.
Skilldrick

4
@fmsf-이것은 실제로 언어 독립적 질문입니다. 런타임에 컴파일러를 호출하여 eval을 시뮬레이션 할 수 있으므로 정적 컴파일 언어에도 적용됩니다.
Daniel Earwicker

1
이 경우 언어는 중복입니다. 나는이 arround와 같은 것을 많이 보았습니다.
fmsf

9
PHP eval은 Lisp eval과 다릅니다. 봐, 그것은 문자열에서 작동하며 URL의 악용은 텍스트 괄호를 닫고 다른 괄호를 열 수 있는지에 달려 있습니다. Lisp eval은 이런 종류의 물건에 취약하지 않습니다. 샌드 박스를 올바르게 샌드 박스하면 네트워크에서 입력으로 오는 데이터를 평가할 수 있습니다 (그리고 구조를 수행하기에 충분히 걷기 쉽습니다).
Kaz

2

평가는 악이 아니다. 평가는 복잡하지 않습니다. 전달한 목록을 컴파일하는 함수입니다. 대부분의 다른 언어에서 임의의 코드를 컴파일하는 것은 언어의 AST를 배우고 컴파일러 내부를 파고 들어 컴파일러 API를 파악하는 것을 의미합니다. lisp에서는 eval을 호출하면됩니다.

언제 사용해야합니까? 무언가를 컴파일해야 할 때마다 일반적으로 런타임에 임의의 코드 를 수락, 생성 또는 수정하는 프로그램입니다 .

언제 사용해서는 안됩니까? 다른 모든 경우.

필요하지 않을 때 왜 사용하지 않아야합니까? 가독성, 성능 및 디버깅에 문제를 일으킬 수있는 불필요하게 복잡한 방식으로 작업을 수행하기 때문입니다.

예,하지만 초보자라면 어떻게 사용해야하는지 어떻게 알 수 있습니까? 항상 함수로 필요한 것을 구현하십시오. 그래도 작동하지 않으면 매크로를 추가하십시오. 그래도 작동하지 않으면 평가판!

이 규칙을 따르면 당신은 평가와 악을하지 않을 것입니다 :)


0

나는 Zak의 대답을 매우 좋아 하며 문제의 본질을 얻었습니다. eval 은 새로운 언어, 스크립트 또는 언어 수정을 작성할 때 사용됩니다. 그는 실제로 더 이상 설명하지 않으므로 예를 들어 보겠습니다.

(eval (read-line))

이 간단한 Lisp 프로그램에서 사용자에게 입력하라는 메시지가 표시되면 입력 한 내용이 모두 평가됩니다. 사용자가 어떤 기능을 입력해야하는지 모르기 때문에 프로그램을 컴파일 하면 전체 심볼 정의 세트 가 작동 해야합니다. 즉,이 간단한 프로그램을 컴파일하면 결과 바이너리가 거대해질 것입니다.

원칙적 으로이 이유 때문에이 것을 편집 가능한 진술로 간주 할 수도 없습니다. 일반적으로 eval 을 사용 하면 해석 된 환경에서 작동하며 코드를 더 이상 컴파일 할 수 없습니다. eval 을 사용하지 않으면 C 프로그램처럼 Lisp 또는 Scheme 프로그램을 컴파일 할 수 있습니다. 따라서 eval 사용을 커밋하기 전에 해석 된 환경에 있고 원하는지 확인해야합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.