SturdyCobble's Study Note

[프로그래밍] 6.1 기억 부류 지정자 (1) 본문

휴지통/['19.06-'20.07]프로그래밍101

[프로그래밍] 6.1 기억 부류 지정자 (1)

StudyingCobble 2020. 1. 20. 14:07

NOTICE : 독학인 만큼, 잘못된 내용이 있을 수 있습니다. 내용 지적은 언제나 환영합니다.

더 이상 이 블로그에는 글을 올리지는 않을 예정입니다. 그렇지만 댓글을 달아주시면 최대한 답변드리고자 노력하겠습니다.


※이 글은 프로그래밍 언어에 대한 기초적인 이해를 가정하고 있습니다. 최소 프로그래밍 언어 하나 정도를 약간이라도 접해보시는 것을 추천합니다. 또한, 이 글은 심화 내용은 되도록 피하여 서술했습니다.

 

(제목의 기억 부류 지정자는 Storage Class Specifier를 번역한 것입니다. 기억 영역 클래스 지정자, 기억 클래스 지정자 등 다양한 번역이 존재하지만, 클래스라고 번역하는 것은 class 키워드로 지정되는 그 클래스와 혼동될 우려가 있어 위의 용어로 사용하였습니다. Microsoft Docs에서는 스토리지 클래스 지정자로 표기하고 있습니다.)

 

이번 글의 범위는 다음과 같습니다.

  C/C++ Java Python
기억 부류 지정자
전역 extern (별도의 개념 존재X) global
지역 auto 해당 개념 존재
정적 static static  
레지스터* register    
typedef typedef    
형 한정자(type qualifier)
(여러 종류가 있지만, 이 글에서는 volatile만 다루었습니다. register의 개념과 대조하기 위해 같이 설명할 예정입니다.)
volatile volatile volatile  
* register 변수라고 해서 무조건 레지스터에 저장되는 것이 아닙니다. 일단 그 공간도 적기도 하며, 공간이 없다고 에러가 나지도 않습니다. 좀 더 빨리 접근할 수 있도록 우선 순위를 준다고 보면 편합니다.

 

 변수나 함수 등이 사용되려면, 일단 어딘가에는 저장이 되어야 합니다. 이는 CPU 내부의 공간일수도 있고(레지스터), 우리가 주기억장치로 사용하는 RAM일수도 있습니다. HDD나 SSD같은 보조 기억 장치에 저장하는 건 파일 입출력이며, 프로그램 실행중에 사용할 변수가 이런 곳에 저장되지는 않을 겁니다. 

 

 또한, 변수가 저장되더라도, 프로그램이 실행되는 내내 저장해둘 수도 있고, 사용되는 순간에만 저장하고 삭제할 수도 있습니다. 이러한 차이는 위에 서술한 많은 종류의 기억 부류 지정자를 필요하게 합니다.

 

 하지만, 프로그래밍 언어의 차이는 이 중 일부를 쓸모가 없게 만들기도 합니다. 예를 들면, 레지스터 변수의 경우, Java와 Python에서는 존재하지 않습니다. 이는 두 언어의 경우 여러 운영체제에서의 호환성을 높이기 위해 별도의 메모리 관리 스템이 메모리를 관리하기 때문입니다. 따라서, 적절히 알아서 메모리를 할당하게 됩니다.

 

 먼저 이 글에서는 전역 변수와 지역 변수에 대해 다루고, 다음 글에서는 정적(static) 변수와 typedef에 대해 다뤄보겠습니다.

(다다음 글에서는 register, volatile와 그 외 메모리에 관한 점들을 다룰 예정입니다.)

 

 전역 변수와 지역 변수의 차이점은 적용 가능한 지역입니다. 전역 변수는 어디서든지 접근이 가능하고, 지역 변수는 특정 구역 내에서만 접근이 가능합니다.

 이렇게만 보면 전역 변수가 좋은 것처럼 보이지만, 전역 변수는 프로그램 실행 내내 존재하기 때문에, 최적화 면에서 좋지 않을 수 있습니다. 또한, 지역 변수는 적용 범위만 다르면 이름이 같아도 상관없지만, 전역 변수는 그렇지 않아서 생기는 관리나 코딩 상 어려움도 존재합니다.(다른 코드를 가져다 쓰는 상황이라면, 수 백개에 달하는 전역 변수를 일일이 이름을 바꾸는 상황이 생길 수 있습니다.) 그 외 여러가지 단점이 있기에, Java에서는 직접적으로 전역 변수를 지원하지는 않습니다.

 

 또 다른 차이점은 선언되는 지점입니다. 아래 코드는 C/C++과 Python에서 전역 변수 gloVal과 지역 변수 locVal이 정의되는 모습을 보여줍니다.

int gloVal;

int main(){
return 0;
}

void func01(){
  int locVal = 0;
}

gloVal = 0

def func01:
    locVal = 0

 

각 예시에서 볼 수 있듯이 어느 함수 안에 속하지 않은 지점에서 선언되면 전역 변수이고, 그렇지 않으면 지역 변수입니다. 특히 C/C++에서는 전역변수는 선언과 동시에 0으로 초기화됩니다. 이는 전역 변수의 경우, 지역 변수와 달리 초기화를 하지 않으면 큰일이 날 수 있는 상황에 많이 사용되기 때문으로 보입니다.

 

 각 변수는 통용되는 범위에도 차이가 있습니다. 다음의 예시를 참고해봅시다.

# include <iostream>

using namespace std;

int gloVal;

int main(){
  cout << gloVal << endl;
  {
    int locVal;
    cout << locVal << endl;
  } // 지역 변수는 { }로 둘러싸인 블럭 내에서만 통용됩니다.
  return 0;
}

-------------------RESULT--------------------
0
4200955

gloVal = 0
def func0():
    locVal = 0
    print(gloVal)
    print(locVal)
func0()

-------------------RESULT--------------------
0
0

 

전역 변수는 어디서나 쓸 수 있지만, 지역 변수는 해당하는 함수나 블록에서만 통용됩니다. 따라서 다음의 코드는 에러를 발생시킵니다.

#include <iostream>

using namespace std;

int main(){
  {
    int i = 0;
  }
  cout << i << endl;
  return 0;
}
-----------------ERROR--------------
ERROR : 'i' was not declared in this scope
cout << i << endl;
^

 

 전역 변수와 지역 변수와 관련된 지정자로 C/C++에선 extern과 auto, Python에서는 global이 있었습니다. 이 중, auto는 거의 쓰이지 않습니다. auto를 안 붙여도 함수 내에서는 지역 변수가 기본이고, 함수 밖에서 auto를 붙이면 에러가 발생하기 때문입니다. 즉 위의 예시에서 auto int i;와 같이 지정할 수 있지만, 그럴 필요가 없습니다.

 

 extern(C/C++)과 global(Python)의 용법은 조금 더 복잡합니다. 지역 변수를 전역 변수로 만드는 개념이 아니라, 안에서 밖에 전역 변수가 있다고 알려주기 위해 쓰이기 때문입니다. 그리고 C++과 Python에서 사용하는 이 두 키워드에도 차이가 있습니다.

 

일단 Python의 global문입니다. 이를 이해하기 위해서 전역 변수에 관한 다음의 예시를 참고해봅시다.

var01 = 10
def func01():
    var01 = 1
    print(var01)
func01()
print(var01)
---------------RESULT--------------------------
1
10

분명 var01을 수정한 것 같은데, 전혀 값이 바뀌지 않았습니다. 이 경우 var01이라는 전역 변수와 func01에서만 통용되는 전역 변수 var01이 따로 존재하게 됩니다. (C++에서는 블록 내부에서 외부의 전역 변수를 수정할 수 있습니다.) 따라서 함수 내부에서는 전역 변수 var01이 가려지게 됩니다.

 

이런 경우 global을 통해 우리가 함수 내에서 사용한 var01이 사실은 전역변수라는 사실을 알려줄 수 있습니다.

var01 = 10
def func01():
    global var01
    var01 = 1
    print(var01)
func01()
print(var01)
------------------RESULT------------------
1  

 

전역 변수의 값이 변경됨을 확인하였습니다. 하지만, 함수 내부에서 전역 변수 값을 수정하는 것보다 지역 변수를 사용하는게( 혹은 class의 멤버 변수를 통해 관리하는 게 ) 코드는 조금 늘어나도, 나중에 효율적으로 관리하는 데 도움이 된다고 합니다.

 

 

 C/C++의 extern 지정자도 비슷하게 선언된 변수가 전역 변수라는 점을 알려줍니다. 그 전에 다음의 예시를 확인해봅시다.

# include <iostream>

using namespace std;

int main(){
  cout << gloVal <<endl;
  return 0;
}

int gloVal = 0;
--------------ERROR-----------------
ERROR: 'gloBal' was not declared in this scope
cout << gloBal <<endl;
^

 

이처럼 뒤에 선언된 전역 변수를 사용하기 위해서, extern 지정자가 필요합니다. 위 코드는 다음과 같이 대체될 수 있습니다.

# include <iostream>

using namespace std;

int main(){
  extern int gloBal;
  cout << gloBal <<endl;
  return 0;
}

int gloBal = 0;

 

이런 경우는 많이 없습니다.  main함수 위쪽 모든 함수들 위에 전역 변수를 선언하는 것이 일반적이기 때문입니다. 그러나, extern이 필요한 경우가 하나 더 있는데, 외부 모듈에 있는 변수를 사용하기 위해 쓰입니다. 즉, 아직 정보가 없는 상황에서 그 전역 변수에 접근하는 데 사용됩니다. 이는 # include를 통해 가져온 외부 파일에 적용되며, 이에 관한 설명은 나중에 다시 다뤄보겠습니다.

 

C++에서는 지역 변수와 이름이 같아 가려진 전역 변수에 접근할 수 있는 방법을 다음과 같이 제공합니다.

# include <iostream>

using namespace std;

int varr = 0;

int main(){
  int varr = 10;
  cout << varr  << ',' << ::varr <<endl;
  return 0;
}
-------------------RESULT---------------
10,0

 

간단하게 ::변수이름 과 같이 쓰면 됩니다.

 

Comments