본문 바로가기
#2 엑셀 오류 가이드

엑셀 VBA에서 프로시저 호출이 너무 깊을 때(Recursive Call) 오류 해결

by 이세계의엑셀 2025. 2. 22.
반응형
반응형

VBA(Virtual Basic for Applications)는 엑셀에서 자동화 작업, 함수 작성, 이벤트 처리 등 다양한 기능을 구현할 수 있는 도구입니다. 그러나 복잡한 로직을 담은 매크로나, 함수가 서로를 재귀(Recursive) 방식으로 호출할 때, “프로시저 호출이 너무 깊습니다”(“Procedure call is out of stack space” 같은 메시지) 오류가 발생하기도 합니다.

실무에서 “재귀 호출”은 특정 함수를 자기 자신 혹은 서로 간에 순환 참조해 문제 해결을 도모할 때 쓰입니다. 예를 들면 트리 구조 탐색, 대체적(반복) 로직을 간단한 함수 호출로 표현하고 싶을 때 재귀를 활용하는 것이죠. 하지만 엑셀 VBA는 재귀 호출에 대한 스택 크기가 제한적이어서, 지나치게 많은 깊이로 함수가 반복 호출되면 Stack Overflow 상태로 에러가 납니다.

또한, 의도한 것이 아니라 “잘못된 이벤트 호출”이나 “함수 A가 B를 호출하고, B가 다시 A를 호출하는” 무한 루프 구조가 발생하면 당연히 프로시저 호출 깊이가 무한히 늘어나면서 VBA가 중단되는 경우도 있습니다. 이 문제를 해결하려면, 재귀 로직 자체를 수정하거나, 함수 호출 순환 관계를 끊고 코드를 개선해야 합니다.

본 글에서는 “VBA 프로시저 호출이 너무 깊습니다” 오류가 발생하는 원인을 분석하고, 이를 어떻게 방지·해결할지 다양한 측면에서 설명합니다. 재귀 알고리즘을 쓰고 싶은 분이나, 이벤트가 무한히 재귀 호출되는 문제를 겪는 분들께 유용한 가이드를 제공할 것입니다.

 

오류 발생 원인 또는 이유

“프로시저 호출이 너무 깊습니다”는 스택이 초과되었음을 의미하므로, 대표적으로 다음과 같은 상황들이 원인이 됩니다.

원인 예시 상황
잘못된 재귀(Recursive) 함수 설계 함수 A가 자기 자신(A)을 호출하는데, 종료 조건이 없거나 매우 늦게 작동
상호 참조 함수 (서로간 무한 루프) Sub A가 Sub B를 호출, B가 다시 A를 호출, 종료 조건 불명
이벤트 프로시저 간 상호 호출 Worksheet_Change 이벤트가 셀 변경 → 해당 이벤트 다시 일으켜 무한 이벤트 루프
스택 사용량 큰 로직 (대량 변수, ByVal 전달) 함수마다 큰 배열/객체를 인수로 넘기거나 지역 변수로 잡아, 스택을 금방 고갈
VBA 스택 제한 & OS/Office 버전 차이 재귀 깊이 수백 단계를 넘어가면 보통 오류 발생, 64비트 Office도 제한은 존재

결국, 핵심 문제는 “재귀 호출이 적절히 멈추지 않는다”거나, “스택 자원을 빠르게 소진하는 구조”라는 점입니다.

해결방법

아래에서 주요 해결책을 500자 이상씩 구체적으로 살펴봅시다.

  1. 재귀 호출 종료 조건(탈출 조건) 확실히 설정
    재귀 함수를 작성할 때, 가장 중요한 것은 기저 사례(base case) 또는 탈출 조건을 명확히 만드는 것입니다. 예를 들어, 트리를 탐색하거나 디렉토리 구조를 순회할 때, 더 이상 하위 항목이 없으면 재귀를 끝내야 합니다. 만약 이 조건이 누락되거나 논리가 틀어지면, 함수가 무한히 자기 자신을 부르고 결국 스택을 초과하게 됩니다.
  2. 예: Function Factorial(n As Long) As Long If n <= 1 Then Factorial = 1 Else Factorial = n * Factorial(n - 1) End If End Function 따라서 “프로시저 호출이 너무 깊음” 오류가 뜨면, 반드시 재귀 탈출 로직을 재검토해야 합니다. n이 0 이하인가?, 더 이상 처리가 필요한 노드가 없나?, 정해진 최대 깊이를 넘어서면 중단해야 하나? 등 의도에 맞는 종료 조건을 코드에 반영하면, 재귀가 무한으로 진행되지 않게 됩니다.
  3. 이런 전형적 예시에서, If n <= 1이 재귀를 멈추는 조건이 됩니다. 만약 이 부분이 빠지면 영원히 Factorial(n-1) 호출을 반복해 스택 오버플로가 날 것입니다.
  4. 이벤트 재귀(Worksheet_Change 등) 방지: EnableEvents 활용
    엑셀 VBA에서 자주 발생하는 재귀 문제는 “Worksheet_Change” 이벤트가 셀을 변경 → 이로 인해 다시 “Worksheet_Change” 이벤트가 트리거 → 다시 셀 변경 → 무한 루프. 이런 식으로 이벤트 루프가 반복되다 스택이 초과하는 현상입니다.
    Private Sub Worksheet_Change(ByVal Target As Range)
        On Error GoTo CleanUp
        Application.EnableEvents = False
    
        ' 여기에 셀값 수정 로직
        ' ...
    
    CleanUp:
        Application.EnableEvents = True
    End Sub
            
    이렇게 하면 동일 이벤트가 재귀로 다시 호출되지 않으므로, 무한 재귀 오류를 방지할 수 있습니다. 이벤트 외에 “SelectionChange”, “Calculate” 등도 비슷한 방법으로 제어 가능합니다.
  5. 이를 해결하려면, 이벤트 핸들러 내에서 Application.EnableEvents = False를 사용해 일시적으로 이벤트를 꺼놓은 뒤, 필요한 셀 수정 작업을 마치고 Application.EnableEvents = True로 다시 켜주는 로직을 짜면 됩니다. 예:
  6. 스택 소진이 심한 재귀 대신 반복문 등 대체로직 고려
    아무리 올바른 종료 조건이 있다 해도, 재귀 깊이가 수천 단계를 넘어가면 VBA가 제공하는 스택 공간을 초과해버릴 수 있습니다. 특히 VBA는 C/C++ 등과 달리 스택 최적화가 한계가 있어, 재귀 함수를 인라인화하거나 꼬리 재귀 최적화(Tail Recursion Optimization)를 자동 적용하지 않습니다.
  7. 예: Function SumRange(r As Range, i As Long) As Long If i > r.Count Then SumRange = 0 Else SumRange = r.Cells(i).Value + SumRange(r, i + 1) End If End Function 결론적으로, 대규모 반복이 필요한 로직이라면 반복문이 적합하고, 재귀는 “자료구조 상 소규모 깊이”가 보장되는 경우에만 쓰는 게 안전합니다. 재귀가 꼭 필요하면 “최대 깊이 제한” 등을 두어 안전 장치를 마련할 수 있습니다.
  8. 만약 r.Count가 매우 크면, 호출 깊이만큼 스택을 소모해 오류가 납니다. 이런 로직은 For 문이나 Do While 등 반복문으로 작성하면, 스택을 과도하게 사용하지 않고 해결 가능합니다.
해결방법 주요 내용
명확한 탈출 조건 구현 재귀 함수·프로시저에 기저 사례 설정, 무한 루프 방지
이벤트 재귀 방지 (EnableEvents) Worksheet_Change 등 내부에서 이벤트 비활성화 후 작업
반복문 등 대체 구현 대규모 호출은 스택 부담 커, 재귀 대신 루프 등 사용 고려

이 원칙만 지켜도 재귀로 인한 “프로시저 호출이 너무 깊습니다” 오류를 크게 줄일 수 있습니다.

 

팁과 예방방법 등

엑셀 VBA는 다른 언어 대비 스택 용량이 그리 크지 않아, 재귀 로직으로 복잡한 작업을 하다 보면 오류가 쉽게 발생할 수 있습니다. 아래 세 가지 이상의 예방법을 통해 재귀 호출에서 발생하는 문제를 미연에 막거나 줄일 수 있습니다.

  1. 디버깅 시 스택 깊이 모니터링 & 로그 출력
    재귀 함수를 만들었을 때, 예상보다 호출이 많이 일어나는지 모르면 문제를 미리 캐치하기 어렵습니다. 따라서 함수 내에 Debug.Print를 넣어 “현재 재귀 깊이(depth)를 출력”하는 식으로 간단한 로그 시스템을 운영하면, 어느 시점에서 깊이가 과도해지는지 모니터링이 가능합니다. 예:이런 식으로 하면 Immediate 창에서 깊이가 얼마나 누적되는지 실시간 관찰할 수 있고, 임계치를 초과하기 전에 중단점을 걸거나 로직을 수정할 수 있습니다. 또한, 무한 루프에 빠지기 전에 depth가 이상하게 증가한다면 탈출 조건을 재검토해야 하겠죠.
  2. Static recursionLevel As Long Function MyRecursiveFunc(...) recursionLevel = recursionLevel + 1 Debug.Print "Depth: " & recursionLevel ' ...재귀 본문... recursionLevel = recursionLevel - 1 End Function
  3. 이벤트 함수에서 주기적 EnableEvents 해제 로직 삽입
    이벤트 기반 재귀가 빈번히 문제가 되는 Worksheet_Change, Worksheet_Calculate, Workbook_BeforeSave 등에서, 조건부로 Application.EnableEvents = False를 끄고, 작업 후 True로 돌리는 습관이 중요합니다. 조건 없이 이벤트 핸들러 안에서 셀을 변경하면, 재차 이벤트가 트리거되어 무한 루프가 일어날 소지가 큽니다.
  4. 예: Private Sub Worksheet_Change(ByVal Target As Range) If Not some_condition Then Exit Sub '이벤트 계속 돌지 않도록 필터 Application.EnableEvents = False On Error GoTo cleanup ' ...셀 값 수정... cleanup: Application.EnableEvents = True End Sub
  5. “some_condition”으로 이벤트를 제한할 수도 있고, 무조건 작업 전에 EnableEvents를 False로 꺼서 반복 호출을 방지할 수 있습니다. 이런 설계로 하면 무한 재귀 문제가 훨씬 줄어듭니다.
  6. 재귀가 필요한 로직은 최대 깊이나 분할치 설정
    때로는 자료구조 특성상 재귀가 불가피합니다. 예: 계층 구조를 탐색해야 하거나, 이진트리·그래프를 순회해야 하는 등. 이럴 땐 함수 호출 전 단계에서 현재 깊이(DepthCounter)를 인자로 전달하면서, If depth > MAX_DEPTH Then Exit Function 같은 식으로 제한을 두는 방법이 있습니다.
  7. 예: Function TraverseTree(node As MyNode, depth As Long) If depth > 200 Then MsgBox "Too deep, stopping recursion." Exit Function End If ' ...재귀 처리... ' TraverseTree node.Left, depth + 1 ' TraverseTree node.Right, depth + 1 End Function 또한 대규모 탐색에 반복 구조(Queue/Stack 자료구조를 VBA에서 구현)로 전환해, 스택 대신 힙/배열을 써서 처리하는 것이 더 낫습니다. 재귀는 코드를 간결히 해주지만, VBA의 스택 한계를 넘어서면 결국 실패하므로 주의가 필요합니다.
  8. 이렇게 하면, 스택 오버플로 이전에 임의로 중단해 안전을 확보할 수 있습니다. 물론 이는 근본 해결책이라기보단 “무한 루프 방어”용으로 쓰는 임시 방안이 될 수 있습니다.
예방 방법 효과
디버깅 시 깊이 모니터링 재귀 함수가 어디서 몇 단계까지 가는지 파악해 조기 대응
이벤트 함수 EnableEvents 제어 Worksheet_Change 등에서 재귀 루프 발생 방지
최대 깊이/분할치 설정 무한 루프 대응, 스택 오버플로 이전에 중단 or 반복문 대체

이 같은 예방법을 일상적으로 적용하면, VBA 재귀로 인한 “프로시저 호출이 너무 깊습니다” 오류를 상당히 줄이고, 코드를 안정적으로 운용할 수 있습니다.

 

FAQ

다음은 “엑셀 VBA에서 프로시저 호출 깊이 초과” 오류와 관련해 자주 묻는 질문 5개를 간략히 정리했습니다.

  1. Q1. “Out of stack space” 에러 메시지를 받았는데, 그럼 자동으로 재시도하거나 스택 늘릴 수 없나요?
    A1. VBA 자체적으로 스택 크기를 동적으로 확장하지 못 합니다. C/C++과 달리 컴파일 시 스택 사이즈를 지정하는 방식도 없고, 런타임에 “스택 늘리기” 같은 옵션이 존재하지 않습니다. 따라서 코드를 개선해서 재귀를 줄이거나, 반복문으로 바꾸거나, 깊이를 제한해야 합니다.
  2. Q2. 이벤트 간 상호 호출이 발생했는데, 어디서 문제되는지 쉽게 찾을 방법이 있나요?
    A2. 엑셀 “Worksheet_Change”나 “Worksheet_Calculate” 등이 상호 호출되는 경우, 코드에 Debug.PrintMsgBox 등 로그를 심어 단계별 호출을 추적하거나, EnableEvents = False를 테스트로 써서 어느 지점에서 이벤트가 다시 불리는지 확인해야 합니다. VBA에서 콜 스택을 직접 보는 건 힘들지만, 단계별 브레이크포인트(F8)로 디버그하면 흐름을 파악할 수 있습니다.
  3. Q3. 재귀 함수가 꼭 필요한데, 깊이가 큰 케이스도 처리해야 합니다. 어떻게 하죠?
    A3. VBA로 깊이가 수천 이상 가는 재귀를 돌릴 수는 사실상 어렵습니다. 반복문 + 명시적 Stack/Queue 구조를 써서, 자체적인 자료구조를 이용해 시뮬레이션하는 방식을 권장합니다. 예를 들어 DFS(깊이 우선 탐색)을 할 때, Collection이나 Dictionary를 써서 방문할 노드를 관리하고, 루프 돌면서 처리하는 식으로 바꾸는 것입니다.
  4. Q4. Excel 64비트 버전을 깔면 스택 용량이 늘어나 재귀가 더 깊이 가능하지 않을까요?
    A4. 64비트 Excel이라도 내부 VBA 스택 제한이 완전히 달라지는 것은 아닙니다. OS에서 64비트 프로세스가 쓸 수 있는 가상 메모리는 커지지만, VBA 엔진의 기본 스택 사이즈가 획기적으로 커지진 않습니다. 어느 정도 여유가 있을 수 있지만, 극적인 차이를 기대하기는 힘듭니다. 코드 개선이 우선입니다.
  5. Q5. 재귀 호출 시 ByRef 인수 대신 ByVal 쓰면 스택이 덜 소모되나요?
    A5. 반대로 ByVal로 넘길 경우, 해당 인수가 복사되어 스택에 더 많은 메모리가 쌓일 수 있어, 오히려 스택 사용량이 늘어날 수 있습니다. ByRef는 주소만 넘기므로 조금 더 스택을 절약할 수 있지만, 큰 배열·객체를 인수로 넘긴다면 그 자체가 문제가 될 수 있죠. 스택 절약 목적이라면, 전역(모듈 수준) 변수를 쓴다거나, 인수로 넘기지 않는 식으로도 고려해볼 수 있습니다. 다만 본질적인 해법은 아니라는 점을 유의해야 합니다.

이 FAQ를 통해 “VBA 재귀/함수 호출 깊이 초과” 문제는 기본적으로 코드 구조 개선이 해법이라는 점을 다시 한 번 확인할 수 있습니다.

반응형