App Programming/C/C++

[추천] auto, register, static extern, global, const, volatile 비교

BAGE 2009. 1. 10. 08:37



C에서 변수(Variable)를 선언하는 syntax를 간단히 표현하자면 다음과 같다.

C++도 비슷하지만 조금은 다르므로, 여기선 C에 한정해서 설명한다.

-> (storage-class-specifier)  (type-qualifier)  type-specifier+

1.

Storage-class-specifier란 말 그대로 변수를 어디에 저장하느냐에 따른 분류라고 생각하면 쉬울 듯하다.

그 종류에는 auto, register, static, extern, typedef, __declspec (<- 요놈은 MS C에서만..)이 있다.

여기서 typedef와 __declspec은 다른 4개와는 의미상 다르므로 생략한다. (나중에~ㅋ)


그렇다면, 저장 장소에는 어떠한 것들이 있느냐~

프로그램을 실행했을 때, 프로그램의 코드 및 변수들은 메모리(RAM)에 올라오게 되는데,

메모리에는 code 영역, data 영역(정적), stack 영역, heap 영역(동적)이 있고,

CPU에는 register 영역이 있다.

code 영역에는 코드 부분이 기계어로 바뀌어 올라오고,

data 영역에는 global 이나 static처럼 프로그램 끝날 때까지 lifetime을 가지는 변수들이 올라온다.

그와 함께 stack은 local 변수들을 위해 사용되고,

heap은 프로그램 실행 중에 (프로그래머에 의해) 동적으로 할당되는 변수들을 위해 사용된다.

CPU의 register는 보다 빠른 연산을 수행할 때 사용된다.


변수는 크게 internal level(함수나 블록 안에 위치)과 external level(모든 함수와 블록의 바깥에 위치)에 선언할 수 있다.

internal level에서는 4개의 키워드 중 어떠한 것도 사용 가능하며, 생략됐다면 default는 auto이고,

여기서 선언된 변수들은 변수가 선언된 함수나 블록 밖에서는 접근할 수 없다 (no-linkage).

external level에서는 static과 extern만 사용 가능하며, 생략도 가능하고,

여기서 선언된 변수들은 모두 프로그램 종료시까지 lifetime이 유지된다.

또한, 함수는 external level에서만 정의될 수 있으며, 항상 global lifetime을 가진다.


키워드를 하나씩 살펴보면,,

autolocal lifetime(선언된 블록이나 함수를 벗어나면 사라짐)을 가지는 변수를 선언할 때 사용한다.

즉, 실행 중에는 메모리의 stack 영역에 위치하게 되고,

internal level에서만 사용 가능하기 때문에 no-linkage(정의된 블록이나 함수 밖에서는 접근할 수 없는)이고,

default 값이다.

흔히 사용하는 local 변수의 경우, 프로그래머가 어떠한 명시도 하지 않으면,

단어 뜻처럼 컴파일러가 자동으로 붙여주는 키워드이다. 그래서 잘 쓰지 않는다.

그리고 값이 자동으로 초기화되지는 않으므로, 초기화 부분이 있어야 한다.

ex> auto int a;

      int a;        // 두 선언은 같다. 둘다 쓰레기값이 들어가 있는 상태.


register는 변수를 CPU의 register에 저장하도록 요청할 때 사용한다.

여기서 '요청'이라는 의미는 상황에 따라서 컴파일러가 요청을 받아들이지 않을 수도 있다는 뜻이다.

레지스터가 남아있지 않을 수도 있으므로.... 그럴 경우엔 auto 변수로 취급하게 된다.

이 녀석도 마찬가지로 local lifetime을 가지며,

internal level에서만 사용 가능하므로 no-linkage이고,

요청이 받아들여지면 실행 중에는 CPU의 register에 위치하게 된다.

또한, ANSI C에서는 register로 선언한 변수 또는 object에 주소 연산자(&)를 허용하지 않는다.

주소가 없기 때문에... 당연한 것...

하지만, C++에서는 그러한 제한이 없다고 한다.

컴파일러가 알아서 레지스터에서 메모리로 위치를 옮겨버리기 때문.

또한, 배열이나 구조체, static 변수에도 사용할 수 없다.


staticglobal lifetime(프로그램 종료때까지 유지됨)을 가지는 변수를 선언할 때,

또는 함수나 global 변수가

internal-linkage(정의된 파일 안에서는 접근 가능하지만, 다른 파일에서는 접근하지 못하는)를 가지도록 할 때 사용한다.

이 녀석은 컴파일 시간에 바인딩이 되어 stack이 아닌 정적 data 영역에 위치하는데,

internal level에서 선언된 static 변수는 no-linkage이고,

external level에서 선언된 static 변수는 internal-linkage를 가진다.

그리고, static은 초기화 부분이 없으면 0으로 초기화된다.

그렇다면, 여기서 비교~~

첫번째, internal level에서 선언된 static  VS  internal level에서 선언된 auto (local 변수).

둘 다 no-linkage이다.

하지만, static 변수는 global lifetime을 가지고, auto 변수는 local lifetime을 가진다.

두번째, external level에서 선언된 static  VS  external level에서 키워드가 생략된 채 선언된 변수 (global 변수)

둘 다 global lifetime을 가진다.

하지만, 하나의 프로그램이 여러 개의 파일로 이루어져 있을 때,

static 변수는 internal-linkage(변수가 선언된 파일에서만 접근가능)를 가지지만,

global 변수는 external-linkage(모든 파일에서 접근이 가능)를 가진다.

함수의 경우에는 external level에서만 선언이 되므로, external level에서 선언된 static 변수와 같은 성질을 가진다.


extern은 다른 곳에서 정의된 변수나 함수를 reference한다는 것을 선언할 때 사용하는 것으로,

함수 또는 변수가 external-linkage를 가지게 한다.

extern으로 reference한 것이 유효하려면, reference된 변수는 external level에서 단 한번은 정의되어 있어야 하므로,

extern으로 선언한 변수는(internal level에서든 external level에서든) 정적 data 영역에 위치하게 되어 global lifetime을 가진다.

컴파일 시간에는 이러한 변수나 함수가 다른 곳에 있는 것으로 간주하고 그냥 넘어가지만,

링크 시간에 다른 곳에 있는 것들과 링크되면서 바인딩이 된다.

또한, 함수는 키워드가 생략되었을 경우, default로 extern이므로,

외부에서 접근을 못하게 하려면 static을 사용해야 한다.



2.

Type-qualifier는 변수의 값을 수정할 수 있느냐의 여부에 따른 분류이다.

여기엔 2가지 종류가 있는데, const와 volatile이다.


const는 변수나 오브젝트가 수정될 수 없다는 것을 나타내고,

volatile은 외부 프로세스(OS, HW, Thread 등)에 의해서 수정될 수 있다는 것을 나타낸다.


그럼, 아무것도 명시하지 않은 것과 volatile과는 뭐가 다른가??

외부 프로세스에 의해서 수정될 수 있다는 것.... 프로그래머가 값을 수정하는 것과는 다른 의미이다.

조금 다르게 표현하자면, "이 놈은 수정될 수 있는 놈이야~"는 것을 알려주는 것이다.

뭔가 다른 느낌...ㅋ 그럼 왜 알려주나??

컴파일러는 프로그래머의 코드를 컴파일할 때, 최적화를 수행한다. (물론, 항상 그런건 아니지만~;;)

하지만, volatile로 선언한 것은 컴파일러가 최적화를 수행하지 않는데,

외부 프로세스에 의해서 언제든지 수정될 수 있기 때문에, "최적화하지 말고 그냥 놔두라~"는 의미이다.

그럼, 왜 최적화하지 말라는 건가..??  값이 수정되는거랑 최적화랑 뭔 상관??ㅋ

똘똘한 컴파일러는 의미없고 쓸데없는 문장을 제거하거나 더 간단히 수정해버리는데,

그렇게 될 경우 예기치 못한 일이 발생할 수가 있다.

즉, 프로그래머가 시키지도 않은 일을 컴파일러가 자기 딴엔 잘 해보겠다고 했는데, 문제가 생긴 것이다.


간단한 예를 들자면,,

int i=0, result;

while (i < 1000000){

   result=100;    i++;

}

이라는 문장이 있을 경우, 컴파일러는 최적화를 하면 다음과 같이 동작하도록 컴파일 코드를 생성한다.

int i=0, result;

result=100;

while (i < 1000000){

   i++;

}

일반적으로 생각하면 result가 while문 안에서 값이 바뀌지 않으므로, 최적화된 것이 맞다.

똘똘한 컴파일러~ㅋ

하지만, 외부 프로세스에 의해서 수정이 가능하다는 것은, result라는 값이 while문을 도는 중에,

외부 프로세스에 의해 interrupt가 걸리고 값이 뜬금없이 500으로 바뀔 수 있다는 것이다.

그러한 경우, 위처럼 최적화를 해버리면 전혀 다른 결과가 나오게 된다.


그렇기 때문에, 외부 프로세스에 의해 값이 바뀔 수 있다는 것을 프로그래머가 알려주면,

컴파일러는 그 부분에 대해서는 최적화를 하지 않고 그대로 둔다.

그 역할을 하는 것이 volatile이다.



3.

Type-specifier는 흔히 알고 있는 type에 대한 것이다.

부호와 관련된 signed, unsigned와

구체적인 타입을 나타내는 void, char, short, int, long, float, double,

집합 형태의 struct, union, enum이 그것이다.