Search
💭

재귀와 정적변수 - JVM 과 변수의 Scope

태그
Java
JVM
목차

백준 4779

public class PROB4779 { static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); static StringBuilder sb; static int number; static int index; public static void main(String[] args) throws IOException { while (true) { String temp = br.readLine(); if (temp == null) break; number = Integer.parseInt(temp); System.out.println(recursion(number)); } } public static String recursion (int number) { sb = new StringBuilder(); index = (int) Math.pow(3, number - 1); if (number == 0) { return "-"; } for (int i = 0; i < index; i ++) { sb.append(" "); } String str = recursion(number - 1); return str + sb.toString() + str; } }
Java
복사
input: 1 result: - - // answer my result: --
Plain Text
복사
public class PROB4779 { static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); static int number; static int index; static int blankIndex; public static void main(String[] args) throws IOException { while (true) { String temp = br.readLine(); if (temp == null) break; number = Integer.parseInt(temp); System.out.println(recursion(number)); } } public static String recursion (int number) { StringBuilder sb = new StringBuilder(); index = (int) Math.pow(3, number - 1); if (number == 0) { return "-"; } for (int i = 0; i < index; i ++) { sb.append(" "); } String str = recursion(number - 1); return str + sb + str; } }
Java
복사
input: 1 result: - - // answer my result: - -
Plain Text
복사
이 두 코드의 차이는 stringBuilder 가 static 이냐 아니냐에 차이가 있습니다. 그럼 왜 static 일 때는 알맞은 값이 나오지 않았던 걸까요?

JVM 의 구조

JVM 은 메모리 영역, 스택 영역, 힙 영역… 으로 이루어져 있습니다.
 세 영역의 역할은 이곳을 참고 해주세용
첫번째 코드의 StringBuilder 의 경우 정적변수이기 때문에 Method Area 에 저장이 됩니다. 그리고 StringBuilder 를 새로 생성하면 생성된 객체가 Heap 영역에 할당됩니다. 그리고 .append() 와 같은 메서드를 만날경우, 아래와 같은 일이 발생합니다.
1.
sb 변수는 힙 메모리 내에 저장된 특정 StringBuilder 객체의 주소를 참조합니다.
2.
.append 메서드 호출을 통해 해당 StringBuilder 객체의 내부 상태(즉, 저장된 문자열)가 변경됩니다.
 힙 내에 있는 해당 StringBuilder 객체의 메모리 주소 자체는 변경되지 않습니다. 대신 그 주소에 위치한 객체의 내부 데이터가 수정됩니다.

그래서 결론은 뭔가요?

모든 재귀 호출에서 동일한 sb 참조?

new StringBuilder() 를 사용했기 때문에 항상 새로운 StringBuilder 객체가 힙 메모리에 할당되고, 새로운 메모리 주소가 생성됩니다. 그러나 문제는 recursion 메서드 내부에서 sb 가 새로운 StringBuilder 로 초기화된 후에 재귀적으로 recursion 메서드가 호출되는 방식입니다. 이 때문에 이전에 sb 에 할당되었던 StringBuilder 객체의 참조를 잃게 되고, 새로운 재귀 호출에서 생성된 StringBuilder 객체의 참조로 덮어씌워지게 됩니다.

디버그 모드로 봅시다.

새로운 객체가 생성 되면서 이전의 참조를 잃고 새로 생긴 객체의 참조로 덮어 씌어지게 됩니다.
따라서 sb 가 “ “ 가 아닌, “” 이 되는 것을 볼 수 있습니다.
이와 같이 변수를 정적변수로 선언할 경우 한번 생성되면 계속해서 동일한 메모리 주소를 참조하게 됩니다! 따라서 정적변수를 두번째 코드와 같이 지역변수로 수정해주면 각 재귀 호출 마다 별도의 StringBuilder 객체와 그 참조가 사용되므로 이런 문제가 발생하지 않습니다.

회고

요즘 코딩테스트 문제를 풀면서 미리 정적변수로 변수를 선언하고 문제를 푸는 경우가 많은데, 아무생각없이 뺐다가 큰코를 다쳤습니다. 앞으로 무언가를 할때 마다 왜? 를 더 철저히 달아야 겠다는 생각이 들었습니다.