지역 변수 (Local Variable)
목차
변수는 영역이 있습니다. 이 영역이란 개념은 딱딱해 보이지만 내용을 들여다 보면 흥미로운 부분이 있습니다. 이번 포스팅은 OOP의 지역변수에 대해서 설명합니다.
우선 지역변수란 무엇인가? – 를 봤을 때 기존 절차형 프로그래밍과 비교가 필요한데요 C언어의 변수는 크게 전역과 지역으로 나눌 수 있습니다. #include 다음 부분에 선언한 전역변수와 main 함수와 다른 함수들 안에서 선언한 변수들입니다. C에서는 객체가 없으니까 지역변수란 { } 블록안에 있는 것들로 분류가 편합니다.
OOP에서는 조금 다른데 클래스안의 필드는 class 라는 { } 중괄호 안에 있지만 이 클래스의 인스턴스가 살아있는 한 메모리에 남아있습니다. 메모리에 남아있는 시간을 변수의 수명(lifetime, lifespan)이라고 하는데요. 클래스 안에서도 메소드에서 선언한 변수들은 지역변수가 됩니다.
지역변수를 이해라는 직관적인 방법은 같은 이름의 지역변수들을 출력해보는 것 입니다. 지역이 다르면 변수의 이름(식별자)가 같아도 됩니다.
using System; namespace MyApp { public class Program { public static int Main(params string[] args) { int localVar; localVar = 5; System.Console.WriteLine("Main : {0}", localVar); Program p1 = new Program(); p1.localTest(); System.Console.WriteLine("Main : {0}", localVar); return 0; } void localTest() { int localVar; localVar = 7; System.Console.WriteLine("Local : {0}", localVar); { // 같은 이름으로 선언할 수 없다 // int localVar; System.Console.WriteLine("Local Block: {0}", localVar); } System.Console.WriteLine("Local : {0}", localVar); } } } [실행] Main : 5 Local : 7 Local Block: 3 Local : 3 Main : 5
위의 예제는 지역변수의 수명이 메소드의 실행 시간이라는 것을 알 수 있습니다. Main 메소드의 localVar 는 끝까지 유지됩니다. 반면 localTest 라는 메소드 안의 localVar 변수는 메소드 호출이 끝나면 사라집니다. 또 메소드 안에 { } 중괄호를 만들어도 별도의 지역이 생성되는 것은 아닙니다. 메소드가 지역변수의 단위가 되는 것 입니다.
변수는 [타입 식별자 = 초기화값;] 처럼 선언하고 초기화 합니다. int myVar = 10; 이렇게 선언한 다음 라인 부터 변수를 사용할 수 있습니다. 메소드가 호출되고 변수가 선언되면 지역변수로 사용되고 호출이 끝나면 사라집니다. 위의 코드에서는 Main 메소드가 localTest를 호출했습니다. localTest 안의 지역변수가 소멸해도 여전히 Main 메소드 영역의 지역변수는 남아있습니다. 그러다가 Main 메소드도 종료하고 모든 변수들은 사라지겠지요. (프로그램의 종료)
그렇다면 C#에 전역변수는 없는가? 명시적으로 제공하지는 않습니다. OOP 컨셉에는 맞지 않지만 최근 트렌드 때문인지 C# 10에서는 using 지시문에 global 한정자를 사용한 global using 지시문을 추가하였습니다. .NET 6의 템플릿에서 최상위문을 도입하면서 아리까리 한 부분이 있는데 이것을 반기지 않는 사람들도 많기 때문에 주의할 필요가 있습니다. MS에서는 최상위 문 자체가 학습용, 테스트 용이라고 하지만 뭔가 C#도 파이썬 처럼 쉽게 사용할 수 있는 언어라는 것을 살짝 어필하는 느낌도 있습니다.
일단 OOP를 공부할 때는 전역변수라는 것은 웬만하면 쓰지 않는게 좋다고 봅니다. 그보다는 namespace의 구조를 짜는데 포커스를 맞출 필요가 있지요. 여기서는 전역변수를 이야기하려는 것은 아니지만 지역변수의 반대 개념이 전역이기 때문에 좀 언급했습니다.
*메소드안에 블록을 중첩하는 경우는 어떨지 예제를 통해 보겠습니다. 메소드가 지역변수 단위기 때문에 같은 식별자로 변수 선언이 안됩니다. 또 바깥 블록에서는 안쪽에 정의한 변수를 사용할 수 없습니다. 설령 안쪽 블록이 끝났다고 할지라도 바깥에서 선언하면 안쪽 블록을 사용할 수 없습니다. 이점은 C와 C++과는 차이가 있는데요. C/C++에서는 함수안에서 중첩된 블록들이 같은 식별자를 사용하는 것을 허용합니다. C/C++에서 넘어왔다면 주의할 부분입니다.
using System; namespace MyApp { public class Program { public static int Main(string[] args) { int myVar1 = 10; System.Console.WriteLine($"block - myVar1 : {myVar1}"); { // 블록안이라도 정의가 불가능하다 // int myVar1 = 20; int myVar2 = 15; System.Console.WriteLine($"nested - myVar2 : {myVar2}"); } // 블록 외부에서 사용이 불가능하다 // myVar2 = 20; // System.Console.WriteLine($"nested - myVar2 : {myVar2}"); // 이렇게 하면 안쪽 블록의 myVar2를 사용못함 // int myVar2 = 15; System.Console.WriteLine($"block - myVar1 : {myVar1}"); return 0; } } } [실행] block - myVar1 : 10 nested - myVar2 : 15 block - myVar1 : 10
메소드의 지역변수에는 여러모로 까다로운 규칙들이 적용되어 있습니다. 그럴 수 밖에 없는게 지역변수 개념이 활발했던 C언어에서도 거의 포인터로 프로그램을 만들다 보니 영역(scope)을 침범하는 일이 많았고 그로 인해 오류가 잦았습니다. 수십년의 데이터를 통해 컴퓨터 과학자들이 알게된 것은 지역 변수는 최대한 그 영역에서만 사용해야 오류가 적어진다는 것이고 C#이나 자바 등 OOP에서는 이 규칙을 엄격하게 적용하고 있습니다. 거기다가 private, public 등 각종 접근제어자들이 많으니까 하다보면 많이 헷갈립니다.
지역변수는 그 많은 규칙 중에 하나일 뿐이지만 함수가 블록내 선언한 변수를 사용하는 방법입니다. 좀 더 자세하게 들어가서 스택과 힙 메모리에 대해서 알면 좋은데 거기까지는 복잡하니까 우선 변수의 시작점과 끝점에 대해서 잘 파악하도록 합니다. 그게 바로 변수의 영역입니다. 변수가 메모리에 상태를 유지하고 있는 시간입니다.
인스턴스 필드와 지역 변수 비교
인스턴스 필드 | 지역 변수 | |
---|---|---|
수명(Lifespan) | 클래스 인스턴스 생성시 출현, 인스턴스 사용이 다하면 끝 (dispose 까지 GC가 관리한다) | 블록안의 선언 시점부터 유효. 블록이 실행 완료시 끝. C/C++과 달리 메소드 내의 블록 { } 을 중첩시켜도 동일한 식별자는 사용할 수 없다. |
초기화 | 타입의 기본값으로 초기화 한다. 보통의 경우 *참조 타입 – null *값 타입 – 0 혹은 0.0 (초기화를 안하면 warning 한다) | 할당되지 않은 지역변수는 컴파일이 불가능하다. |
메모리 | 클래스의 인스턴스는 힙 메모리에 저장. 따라서 인스턴스 필드는 모두 힙에 저장. 필드가 참조 타입 (ref type)이라도 마찬가지이다 | 값 타입은 스택에 저장한다 참조 타입은 참조 변수는 스택에 실제 데이터는 힙에 저장 |
절차형 프로그래밍에서는 전역과 지역을 비교하는데 OOP에서는 인스턴스와 지역변수를 비교합니다. 위에서 이야기한 것 처럼 전역 필드를 만들려면 만들 수는 있는데 C#의 OOP 설계 개념과는 그다지 잘 맞는다고 볼 수는 없습니다. 다만 예외적인 필요에 의해서 사용할 수 있을 겁니다. C#9에 최상위 문 개념이 나온 것은 얼마 되지 않았는데 이것이 기존 사용자에게는 혼동이 될 수도 있고 신규 사용자를 끌어들이는 요인도 될 수 있습니다만… 좀 지켜볼 필요는 있다고 봅니다.
뭐 어떤 면에서 별도의 OOP 교육 없이도 고급 기술을 쉽게 사용할 수 있는 파이썬이나 뭐든지 가능한 자바 스크립트가 매력적이긴 합니다. 파이썬과 자바 스크립트의 공통점은 이들이 동적 타이핑(dynamic typing)이란 부분이지요.
요약과 추가 설명
지역 변수는 상당히 중요한 개념입니다. (기초에서 안 중요한 내용이 없지만서도…) 현재 실행하는 영역이 어디인가? 변수의 수명이 얼마나 남아있나? 이것들을 시각적으로 나타내면 알기 쉽겠지만 또 다른 번거로운 일이고 프로그래머의 머리속에서 개념이 잡혀있어야 합니다. 원리를 학습한 후 그게 맞는 건지 검증하기 위해 디버거를 사용하면 지역변수 개념을 익히는데 도움이 됩니다. MS의 비주얼 스튜디오에는 훌륭한 디버거가 있기 때문에 큰 어려움은 없을 겁니다.
이번 포스팅에서는 지역변수에 대해서 약간 잡다하게 설명해봤습니다. 생각해 볼 꺼리가 많기 때문에 이 외에도 다양한 자료를 찾아보는 것을 권장하고요. 참고로 지역변수란게 local variable 의 한글 번역입니다. 전역변수는 global variable 인데 일본에서는 국소변수와 광역변수라는 용어를 사용합니다. (영어 표기는 로컬변수, 글로벌변수로 외래어 표기법을 사용)
순수한 개인적인 의견으로 지역변수라는 번역이 적합한지는 잘 모르겠습니다. 일본의 국소변수는 영역을 제한한다는 뜻이 있어서 더 와닿습니다. 병원에 가면 국소마취해서 일부분만 감각을 잃게하는 것 처럼 그런 한정하는 개념이 지역변수에는 없기 때문입니다. 때문에 개념을 이해하는데 더 시간이 걸릴 수 있는데요. 뭐 원리를 알게 되면 용어가 문제되지 않겠지만 처음 배우는 사람들에게는 혼동스러운 부분이 있습니다. 따지고 보면 local 도 비슷한 느낌이긴 한데 그럴 바엔 그냥 외래어인 로컬변수라고 부르는 것도 딱히 나쁘지 않다고 봅니다.
이것도 좀 사대주의인가… 합리적 의심을 해보는데 그냥 지역이라는 말은 변수가 살 수 있는 지역이라는 뜻이므로 어떤 용어를 사용하건 상관은 없긴 합니다.
잡설이 다소 길어졌는데 이 블로그가 지향하는게 다소 잡스럽게 설명하더라도 딱딱한 개념은 피하자는 취지이므로 독자님들의 양해를 구합니다. 기술 블로그라는게 교과서적인 내용을 적어 놓는다면 의미가 없고 MSDN이 공식문서이므로 가장 잘 되어 있습니다. 왜냐하면 다른 모든 기초 강의나 교재들 블로그 포스팅들도 MSDN 공식문서에 기초합니다. 하지만 초보자가 읽기에는 좀 딱딱하지요. 100% 한글화가 되있지도 않은데 최근 문서들은 열심히 한글화를 해주는 것 같은데 또 옛날 문서들은 그냥 번역계획이 없는 것 같기도 합니다. 지금은 온라인 문서 수가 엄청나게 방대하기 때문에 이것들을 사람이 번역할 거라면 한참 걸릴 거니까 번역 프로그램의 성능향상을 기대하는게 나을 듯 하지요.
MS계열 언어의 포스팅을 위해 MSDN을 참고하는데 확실히 딱딱하긴 합니다. 번역도 좀 그렇지만 원문 자체가 딱딱한게 아닌가. 공식적인 MS의 기술 문서들이 최근에는 많이 부드러워지고 있긴 하지만 간결함과 정확성을 추구하는 기술문서의 특징이 재미가 없습니다. 설명 짧으면 빨리 많이 이해할 것 같지만 사람마다 그렇지 않습니다. 그 짧은 내용에는 많은 기초 기술의 내용이 압축 되있기 때문입니다. 많이 아는 사람한테 유리할 뿐 입니다.
이 블로그는 스무디한 포스팅을 추구하기 때문에 간단한 내용도 설명이 길어져서 문제긴 합니다만 한개의 포스팅에서 모든 것을 이해할 수는 없으니까 앞으로는 분량 조절에도 신경을 쓸 생각입니다.
참고링크 MSDN
개발자 도구, 기술 설명서 및 코딩 예제 | Microsoft Docs