2009-03-04 7 views
16

스택 오버 플로우를 피하는 방법에 대한 재귀를 사용할 때 일반적인 규칙이 있습니까?C에서 재귀 사용 #

+0

프로그래밍에 하드 세트 규칙이 있습니까? – Jason

+0

공정한 포인트 - "하드 세트"에서 "일반"으로 질문을 변경했습니다. –

+0

Not C#-specific. 모든 규칙이 모든 언어에 적용됩니다. –

답변

32

당신에 따라 달라집니다 재귀 할 수있을 것입니다 얼마나 많은 시간 :

  • 스택의 크기 (보통 1메가바이트 IIRC,하지만 이진 손으로 편집 할 수 있습니다, 나는 이렇게 권하고 싶지 않다) 재귀 사용의 각 단계 사용중인
  • JIT를 사용 (10 개 캡처되지 Guid 지역 변수와 방법은 예를 들어, 로컬 변수가없는 방법보다 더 많은 스택을 가지고있을 것입니다) 얼마나 많은 스택
  • - 때때로 JIT 을 사용합니다. 꼬리 재귀를 사용합니다. . 규칙은 복잡하고 기억이 안납니다. (거기에 blog post by David Broman back from 2007an MSDN page from the same author/date,하지만 그들은 지금 날짜가있을 수 있습니다.)

어떻게 스택 오버 플로우를 방지하기 위해? 너무 멀리 재발하지 마라 :) 재귀가 매우 멀리 가지 않고 종료 될 것이라고 합리적으로 확신 할 수 없다면 (매우 안전하지만 "10 이상"에 대해 걱정할 것입니다) 재귀를 피하기 위해 재 작성하십시오.

+0

TCO는 64 비트 IIRC에서만 발생한다고 생각합니다. 다른 모든 경우에는 여전히 전화에 꼬리를 붙일 필요가 있습니다. 그리고 규칙을 지키지 않으면 꼬리가 아닐 수도 있습니다. – leppie

+0

@leppie : 내가 기억하지 못했던 정확한 종류의 규칙 :) 블로그 게시물을 파헤쳐 볼 수 있는지 알게 될 것입니다. –

+0

@ 존 여기 내가 찾은 하나 : http://blogs.msdn.com/davbr/pages/tail-call-jit-conditions.aspx –

1

합리적인 스택 크기를 유지하고 실제로 문제가 아닌 작은 문제를 계속 해결할 수 있도록 문제를 나누고 정복하는 것 외에도.

7

나는 하드 세트을 인식하지 못하므로 스택 오버 플로우가 발생하지 않습니다. 나는 개인적으로 보장하려고 노력한다. -
1. 나는 나의 근거가 옳다.
2. 코드가 어느 시점에서 기본 사례에 도달합니다.

+0

Ok Ted. 재귀 함수에는 두 부분 -1이 있습니다. 문제를 줄이고 자체를 호출하는 부분 - 재귀 적 사례. 2. 문제의 가장 작은 인스턴스를 나타내는 부분으로, (보통) 사소한 답 -베이스의 경우가 있습니다. – batbrat

+0

재귀 호출이 끝나고 기본 사례에 도달하면 되감기를 시작합니다. 나는 어떤 시점에서 기본 조건에 도달해야한다고 말하려고했다. – batbrat

+0

http://en.wikipedia.org/wiki/Recursion_(computer_science)는 볼만한 곳입니다. – batbrat

4

많은 스택 프레임을 생성하는 경우 재귀를 루프로 풀어야 할 수 있습니다.

특히 여러 레벨의 재귀 (A-> B-> C-> A-> B ...)를 수행하는 경우 해당 레벨 중 하나를 루프로 추출하여 메모리를 절약 할 수 있습니다 .

+0

+1, 좋은 답변입니다. 루프에서 트리 파싱을 한 것을 보았습니다. 훨씬 빠르고 스택 안전했습니다. –

3

정상적인 제한은 연속 호출간에 스택에 많이 남아 있지 않으면 약 15000-25000 레벨입니다. IIS 6 이상인 경우 25 %

대부분의 재귀 적 알고리즘은 반복해서 표현할 수 있습니다.

할당 된 스택 공간을 늘리는 방법은 다양하지만 먼저 반복 버전을 먼저 찾아 보겠습니다. :)

8

정말로 재귀 알고리즘을 사용하고 있는지에 따라 다릅니다. 그것은 간단한 재귀가 있다면, 당신은 같은 것을 할 수 있습니다

public int CalculateSomethingRecursively(int someNumber) 
{ 
    return doSomethingRecursively(someNumber, 0); 
} 

private int doSomethingRecursively(int someNumber, int level) 
{ 
    if (level >= MAX_LEVEL || !shouldKeepCalculating(someNumber)) 
     return someNumber; 
    return doSomethingRecursively(someNumber, level + 1); 
} 

그것은 재귀의 수준은 논리적 인 한계로 정의 될 수있는이 방법은 정말에만 유용하다는 것을 주목할 필요가있다. 이 문제가 발생할 수없는 경우 (예 : 나누기 및 정복 알고리즘) 단순성 대 리소스 제한의 균형을 어떻게 조정할지 결정해야합니다. 이런 경우 미리 정의 된 한도에 도달하면 메소드간에 전환해야 할 수 있습니다. quicksort 알고리즘에서 사용한이 작업을 수행하는 효과적인 방법은 목록의 전체 크기 비율로 수행하는 것입니다. 이 경우 논리 한계는 조건이 더 이상 최적이 아닌 경우의 결과입니다.

+0

+1 - 동일한 말을하려고했습니다 –

+0

이 솔루션이 마음에 들지만 시스템에 종속적이지 않습니까? 일부 시스템은 더 많은 레벨을 처리 할 수 ​​있으며 런타임에이를 판별 할 수있는 방법이 없습니다. 또한, 많은 스택 프레임이 필요하다면, 이걸 가지고 프로그램을 무력하게 만들 수 있습니다. – DevinB

+0

맞아,하지만 한계는 논리적이어야한다. (알고리즘에 기반하여) 기계적 능력에 기초한 물리적 인 것이 아니어야한다. 그렇지 않으면, 당신이 평행을 이룬다면 당신은 벽에 부딪 칠 것입니다. –

1

나는 단지 꼬리 - 재귀를 생각했지만, C#은 그것을 지원하지 않는다는 것이 밝혀졌습니다. 그러나 닷넷 프레임 워크를 지원하는 것 : 당신이 그때는 아마 끔찍하게 잘못 뭔가를하고 있으며, 시스템 제한에 대해 문의해야하는 경우

http://blogs.msdn.com/abhinaba/archive/2007/07/27/tail-recursion-on-net.aspx

+0

제작 된 일리노이가 중요한 모든 것이 아니라는 점에 유의하십시오. JIT *는 일리노이 코드에서 꼬리 호출 최적화를 수행합니다 (Jon의 답변 참조). –

0

는, 기억하십시오.

따라서 일반적인 작업에서 스택 오버플로가 발생할 것으로 생각되면 문제에 대한 다른 접근 방법을 생각해 내야합니다.

반복적 인 함수를 반복적 인 함수로 변환하는 것은 어렵지 않습니다. 특히 C#에는 Generic :: Stack 컬렉션이 있습니다. 스택 유형을 사용하면 스택 대신 프로그램의 힙에 사용 된 메모리가 이동합니다. 이렇게하면 재귀 데이터를 저장할 전체 주소 범위가 제공됩니다. 그만큼 충분하지 않다면 디스크에 데이터를 페이지하기가 어렵지 않습니다. 하지만이 단계에 이르면 다른 해결책을 진지하게 고려할 것입니다.

+1

시스템 제한에 대해 묻지 않고 일부 지식을 얻으려고합니다 .... –

1

기본 CLR에서 실행중인 경우 스레드의 기본 스택 크기는 1MB입니다. 그러나 다른 호스트가이를 변경할 수 있습니다. 예를 들어 ASP 호스트는 기본값을 256KB로 변경합니다. 즉, VS에서 완벽하게 작동하지만 실제 호스팅 환경에 배포 할 때 코드가 손상 될 수 있습니다.

다행히 올바른 생성자를 사용하여 새 스레드를 만들 때 스택 크기를 지정할 수 있습니다. 제 경험상 거의 필요하지 않지만 이것이 해결책이었던 한 사례를 보았습니다.

바이너리 자체의 PE 헤더를 편집하여 기본 크기를 변경할 수 있습니다. 주 스레드의 크기를 변경하려는 경우 유용합니다. 그렇지 않으면 스레드를 만들 때 적절한 생성자를 사용하는 것이 좋습니다.

1

나는 here에 대해 짧은 글을 썼다. 기본적으로 depth라는 선택적 매개 변수를 전달합니다. 깊이가 갈 때마다 1을 더합니다. 재귀 적 방법 내에서 값의 깊이를 확인합니다. 설정 한 값보다 큰 경우 예외가 발생합니다. 값 (임계 값)은 응용 프로그램 요구 사항에 따라 다릅니다.