본문 바로가기

컴퓨터공학/C, C++

C,C++> 기억 부류

기억 부류는 총 네 가지 종류가 있다. 전역, 지역, 정적, 레지스터.

전역변수와 지역변수를 구분하는 게 가장 중요하다. 나머지 두 기억 부류는 전역 지역 부류 특성들을 조금씩 조합한 것이다. 두 가지만 확실히 구분할 수 있으면 기억 부류는 다 아는 것이다. 지금부터 어떻게 다른지 비교해 보자.

1. 변수 선언 위치가 다른데 전역 변수는 함수 바깥이고 지역 변수는 함수 내부에서 선언한다.

2. 전역 변수는 프로그램 전체가 공유한다. 지역 변수는 함수 외부에서는 사용할 수 없다. 

3. 전역 변수는 프로그램 실행 중에는 파괴되지 않는다. 지역변수는 함수와 운명을 같이한다. 

4. 전역 변수는 정적 데이터 영역에 생성된다. 정적 데이터 영역은 프로그램 코드 바로 다음에 위치하는 실행 파일의 한 부분이다. 프로그램이 실행될 때 메모리로 로드되어 실행 중에 계속 유지된다. 지역 변수는 프로그램 실행 중 생성과 파괴를 반복하므로 스택에 생성된다. 지역변수, 인수, 함수 실행 후 돌아갈 번지 등이 스택에 성되었다가 사라졌다를 반복한다. 

5. 전역 변수는 초기식이 없더라도 0으로 초기화된다. 전역변수는 컴파일러에 의해 초기화된 채로 실행 파일에 새겨지므로 초기화에 걸리는 시간은 0이다.  전역 변수는 프로그램 전체에 걸쳐 사용되는 변수이므로 초기화를 지정하지 않더라도 안전을 위해 0으로 초기화한다.

반면 지역변수는 별도 초기식이 없을 경우 초기화되지 않는다. 따라서 무슨 값을 가질 지 알 수 없는데 이걸 쓰레기값이라고 한다. 초기값이 없을 때 지역 변수를 초기화하지 않는 이유는 함수가 호출될 때마다 변수가 새로 생성되기 때문이다. 매변 변수를 초기화하면 그만큼 실행 속도가 느려지므로 초기화하지 않는다.

 

지역 변수의 장점

지역 변수를 사용하면 프로그램 구조화에 도움을 주고 유지, 보수를 쉽게 해주는 장점이 있기 때문이다.

1. 함수 독립성을 높인다. 부품은 가급적이면 스스로 작동할 수 있어야 재활용하기 좋다. 부품끼리 공유하는게 많아지면 좋지 않은 구조를 만든다. 함수를 독립적으로 작성하면 함수끼리 정보 교환은 어떻게 할까? 인수와 반환값을 쓰면 되므로 전역변수를 사용할 필요가 없다. 

2. 디버깅 호율을 높인다.  전역 변수가 편하다고 남발하면 디버깅할 때 봐야할 변수가 많아진다. 전역 변수 통용 범위는 프로그램 전체라서 어떤 함수가 잘못 건드렸는지 찾아내기가 힘들다. 반면 지역 변수는 디버깅하기 쉽다. 왜냐하면 자신이 소속된 함수 안에서만 유효하기 때문이다. 

3. 메모리가 절약된다.

4. 재귀 호출이나 상호 호출같은 기법은 지역 변수가 있어야만 사용할 수 있다. 

전역 변수는 복잡한 문제를 일으킬 수 있기 때문에 가급적이면 자제하는 것이 좋다.  전역 변수를 사용하지 않고 프로그램 작성할 수 있기 때문이다. 

 

외부 변수 

지정자(Specifieer)는 기억 부류, 상수 지정, 최적화 금지 등 여러 가지 성질을 지정하는 키워드이다. 필요없을 경우 생략할 수 있다. 

기억 부류를 지정할 대는 auto, extern, static, register 등의 키워드를 사용한다. 

 

auto

 지역 변수 지정자 이름이 auto인 이유는 자동으로 생성되고 파괴되기 때문이다. 그래서 지역변수를 자동 변수(Automatic Variable)이라고 한다. 지역 변수는 함수 내부에서만 선언할 수 있으므로 함수 외부에서 auto 기억 부류를 지정하면 에러가 된다. 하지만 auto 키워드는 거의 사용하지 않는다. 지정자 생략을 하면 디폴트로 auto가 되기 때문이다. +를 상수 앞에 일일이 붙여서 표기하지 않는 것처럼 auto키워드도 일반적으로 붙이지 않는다. 지정자 없이 변수를 선언하면 변수 선언 위치에 따라 기억부류가 결정된다. 

함수 내부 : auto로 인식하여 지역변수가 된다.

함수 외부 : 전역변수로 인식한다. 

 

extern

함수 외부에 있는 변수를 사용하기 위해 extern 선언을 한다.

함수가 전역 변수를 사용하기 위해서 extern 선언을 하는 것이 원칙적이지만 생략할 수 있다. 

함수보다 앞 쪽에 선언되어 있는 외부 변수는 굳이 extern 선언을 하지 않아도 된다. 

extern 선언은 외부 변수 이름과 타입에 대한 정보를 제공하는데 함수보다 변수 선언문이 앞쪽에 있다면 컴파일러가 이미 이 변수를 알고 있어서 별도 정보 제공을 할 필요가 없는 것이다. 

따라서 함수보다 더 뒤에 있는 변수에 대한 정보를 제공하기 위해서는 extern 선언이 필요하다. 

근데 실제로 전역변수를 보통 소스 앞에 모아서 선언하는게 보통이라서 extern 선언을 할 필요가 없다. 

extern을 선언해야 하는 유일한 경우는 외부 모듈에 있는 전역변수를 참조할 때 extern 선언을 한다. 

 

아무리 전역 변수라도 변수 정보가 공개된 이후에만 사용할 수 있다. 

extern은 변수가 외부 모듈에 작성되어 있다는 것을 알려준다.

알리기만 하기 때문에 정의문이 아니라 선언문이다.

이 선언으로 컴파일러는 변수를 외부 변수로 컴파일한 다음 링크 과정에서 변수의 실제 주소와 연결한다. 

전역 변수는 정적 데이터 영역에 생성되므로 주소를 알고 있으면 프로젝트 어떤 모듈에서도 참조가 가능하다.

그래서 c언어 에서는 전역변수를 외부변수(External variable)이라고 한다. 

 

정적 변수 

정적 변수(Static Variable)는 전역변수와 지역변수 성격을 동시에 갖는 기억부류이다. 

저장 장소는 전역 변수이지만 통용 범위는 지역 변수이다. static을 붙인다. 

특정 함수만 사용하되 그 값을 계속 유지할 필요가 있을 때 정적 변수를 사용한다. 

지역 변수는 함수를 호출할 때마다 스택에 다시 생성되고 함수가 끝날 때 파괴되므로 값을 계속 유지할 수 없다. 

사용 예시는 호출 횟수를 기억하는 카운트 함수를 쓸 때 사용된다. 

외부에서 이 변수를 참조할 수 없다. 

정적 변수는 최초 호출될 때 단 한 번만 초기화되며, 선언문에서 초기화하지 않으면 전역변수와 마찬가지로 0으로 자동 초기화한다. 

함수 내부에서 큰 배열을 선언하고 초기화할 때 초기화 시간을 절약하기 위해 정적으로 선언하여 한 번만 초기화하도록 해야 한다. 그렇지 않으면 함수가 호출될 때마다 큰 배열이 생성, 초기화, 파괴를 반복해서 느려지기 때문이다. 

 

내부 정적 변수

함수 내부에 선언된 정적 변수를 내부 정적 변수라고 한다. 정적 변수는 특성상 특정 함수 전용으로 선언하는 경우가 많아서 보통 정적 변수라고 하면 내부 정적 변수를 의미한다.

 

외부 정적 변수

함수 외부에도 정적 변수를 선언할 수 있는데 외부 정적 변수라고 한다. 일반적으로 전역 변수와 같은 성질을 갖는다. 다른 점은 extern 선언에 의해 외부 모듈로 알려지지 않는다. 즉 자신이 선언한 모듈에서만 사용할 수 있는 모듈 전역 변수이다.  외부 모듈에서 extern 선언을 해도 이 변수를 참조할 수 없다. 따라서 전역 변수이면서 외부에서 알려서는 안되는 변수가 필요할 때 외부 정적 변수를 사용한다. 

 

레지스터 변수

지역, 전역, 정적 변수는 정적 데이터 영역이든 스택이든 메모리에 생성되지만 레지스터 형 변수는 메모리가 아닌 CPU의 레지스터에 저장된다. 32비트 cpu일 경우 저장할 수 있는 변수 타입은 int, unsigned, 포인터형 등의 32비트형 뿐이다. 실수형은 저장할 수 없고 구조체나 배열은 당연히 안된다.

레지스터 개수가 많지 않기 때문에 변수는 두 개까지만 선언할 수 있다. 컴파일러나 플랫폼에 따라서 레지스터형 변수를 위해 할당되는 레지스터가 다르다. 세 개 이상 레지스터형 변수를 선언하면 최초 두 개까지만 레지스터형이 되고 나머지는 지역변수가 된다. 

레지스터는 한정된 자원이라서 일시적으로 사용할 지역 변수만 지정할 수 있다. 따라서 전역 변수는 레지스터를 지정할 수 없다. 

레지스터 변수를 사용하는 이유는 오직 속도 때문이다. 

레지스터 변수는 메모리에 생성되는 게  아니므로 &연산자를 사용할 수 없다. cpu 내부에 있어서 주소값이 없기 때문이다.

다만 레지스터 포인터 변수가 주소값을 기억할 수 있으므로 * 연산자는 가능하다. 

컴파일러마다 다른데 만약 레지스터형 변수에 & 연산자를 사용하면 이를 에러로 처리하지 않고 이 변수를 지역 변수로 만들어 버려서 주소를 갖게 만든다. 개발자 실수를 컴파일러가 알아서 수정해주는 것이다. 

 

정적 함수

함수는 전역, 지역, 레지스터형 기억 부류가 없다. C의 함수는 모두 수평적이고 평등한 관계이다. 함수를 다른 함수의 지역 함수로 선언하는 걸 허용하지 않는다. 따라서 C 함수들은 원칙적으로 전역이다. 

기억 부류 중 함수에 적용하는 건 정적(static) 기억 부류밖에 없다. 정적 함수는  특정 모듈에서만 사용할 수 있다. 외부 정적 변수 특성과 유사하다. 특정 모듈에서만 사용할 수 있어서 외부에서 원형을 선언해도 이 함수를 호출할 수 없다. 

이 기능은 어떨 때 유용할까? 모듈 함수 이름이 겹칠 때 사용하면 된다. 이름 중복이 있으면 헤더 파일 선언할 때 에러가 난다. 컴파일러가 무슨 함수를 써야할 지 모르기 때문이다. 따라서 쓰고 싶은 함수는 냅두고 필요 없는 함수를 정적 선언하면 외부에 알려지지 않으므로 같은 이름을 가지는 함수와 충돌하지 않는다. 

 

통용 범위

C언어에서 전역 변수와 지역 변수 이름이 중복될 때 지역 변수가 선언된 함수에서 전역 변수를 참조할 수 없다. 전역 변수가 지역 변수에 의해 완전히 가려져 있어서 쓸 수 없는 상황을 가시성이 없다고 표현한다. C++에서는 가려져 있는 전역 변수를 참조할 수 있는 범위 연산자가 있다. 연산자 :: 을 사용하면 전역 변수를 읽을 수 있다. 

요약하면 같은 통용 범위에서는 명칭이 중복될 수 없다. 전역, 지역 명칭이 중복되면 지역이 우선이다. 

 

블록 범위 

지역 변수를 함수 내부에 선언된 변수라고 말했지만 엄밀히 말하면 { } 괄호 안 블록에 선언된 변수를 지역 변수라고 한다. 그래서 함수 내부에 { } 블록을 선언해서 또 다른 지역 변수를 만들 수 있다. 대표적인 예시로 함수 내부의 if 문이다. 

함수 범위보다 더 좁은 통용 범위를 블록 범위라고 한다.

이미 선언된 변수명을 다시 쓰고 싶으면 블록을 하나 더 만들고 이 블록 안에 원하는 변수를 선언하면 된다. 

 

선언과 정의 

선언(Declaration) : 컴파일러에게 대상 정보를 알린다. 함수가 어떤 인수를 받고 어떤 타입을 반환하는지 알리는 원형 선언이 대표적인 선언이다. 컴파일러에게 단순 정보만 전달하는 것이므로 본체는 갖지 않는다. 따라서 중복 표현해도 상관없다. 그러나 중복 선언할 때 앞 선언과 뒷 선선이 다르면 안된다. 예를 들면 int Max(...); 라고 했는데 뒤에는 double Max(...) 라고 선언할 수  없다. 

정의(Definition) : 대상 정보로부터 대상을 만든다. 정의로부터 본체를 컴파일하여 코드를 생성한다. 선언을 겸하므로 앞에 있으면 원형 선언을하지 않아도 된다. 정의는 실제를 만들기 때문에 중복되면 안된다.

 

선언과 정의는 명확히 다른 개념이지만 실제는 명확하게 구분하지 않고 사용한다. 지역 변수는 정의와 선언이 일치하며 만든 영역에서만 사용하므로 별도 선언할 필요가 없다. 그래서 지역 변수는 정의만 가능한 대상이지만 "선언한다"라고 하지 "정의한다" 라고 하지 않는다. 

전역 변수 경우에는 int i; 가 정의이고 extern int i; 가 선언으로 구분하지만 전역 변수 정의문인 int i; 도 선언문이라고 한다.매크로의 경우 실제 메모리 할당을 하는 건 아니아서 선언이 맞지만 일반적으로 정의라고 표현한다. 실제로는 별 구분없이 사용하는 경향이 있다. 

 

표준 함수

함수를 익힐 땐 암기 위주 공부보다 레퍼런스를 참고하는 방식으로 하자. 어차피 모든 함수를 암기할 수 없다.

 

'컴퓨터공학 > C, C++' 카테고리의 다른 글

C,C++> 포인터  (0) 2022.07.05
C,C++> 배열  (0) 2022.06.09
C,C++> 함수  (0) 2022.06.06
C,C++> 조건문 제어문 연산자  (0) 2022.06.01
C,C++> 변수 이야기  (0) 2022.05.25