C 프로그래밍 입문/C 관점에서의 변수 혹은 상수와 데이터 타입

C 관점에서의 변수 혹은 상수와 데이터 타입

+/-

먼저 결론부터 이야기 하자면, C의 '변수' 혹은 '상수' 라는 것은 '메모리의 일부'를 이야기 하며, 메모리에 있는 '데이터를 어떻게 다룰지'를 C 컴파일러에게 알려주기 위해 데이터 타입 이라는 것을 사용 한다.

C 라는 언어는 사람 보다는 기계에 가까운 언어이다. 그렇기 때문에 C 라는 언어를 대할 때엔 기계의 관점에서 대하는 편이 이해하기 쉬운 경우가 다반사 이다. 그중에 가장 민감한 부분이 이 바로 데이터를 다루는 것에 관한 문제인데, C를 공부하는 많은 이들이 포인터의 개념을 이해하고 활용하는데 있어 가장 어려움을 느끼는 이유 이기도 하다. 변수와 포인터의 관계를 이해하는데 있어 고전을 면치 못하는 이유는, C 에서 다루는 데이터는 개념적인 것이 아니라 실물인데 사람들은 그걸 개념으로 이해 하려 시도 하기 때문에 점점 복잡해지고 어려워 지는 것이다.

그럼 함께 생각해 보도록 하자, 물리적인 컴퓨터를 볼때 데이터(예를 들어 그림이나 책의 본문)가 쉽게 저장되고, 수정될 수 있는 곳은 다음 세군데 이다.

  1. 하드디스크
  2. 메모리(RAM, ROM)
  3. 레지스터

프로그래머가 아니더라도 하드디스크에 그림이나 책의 본문이 저장된다는 사실은 알고 있을 것이다. 그리고 어느정도 컴퓨터의 하드웨어에 대한 지식이 있는 사람이라면 메모리에 데이터가 저장된다는 사실을 아는 사람도 적지는 않을 것이다. 마지막으로 가장 적은 수의 사람만이 '레지스터'라는 존재와, 레지스터에 데이터가 저장 된다는 사실을 알고 있을 것 이라 생각한다. 레지스터는 CPU 내부에 존재하며, 연산을 하거는 등의 작업을 하려면 반드시 레지스터에 데이터를 넣어야만 한다. 일반적인 CPU 내에 존재하는 레지스터의 수는 적게는 10여개로 부터 100여개의 레지스터까지 다양하다.[1]

레지스터는 예외적으로 이름을 가지고 있는 메모리 이다. 물론 더 하위 영역으로 내려가면 일련 번호를 가지고 있는 경우도 있겠지만, 기본적으로는 이름을 가진 메모리 이다. 이 경우는 예외로 치고, 다른 두 저장장치, 하드디스크와 메모리는 그 안에 데이터를 써넣거나 읽어들일때 일련번호를 이용한다.

먼저 메모리의 경우 메모리의 특정위치를 지정하기 위해 일련번호를 사용한다. 일련번호는 0부터 시작되며 메모리의 크기만큼 숫자가 할당된다. 예를 들어 64K 메모리를 갖는 시스템이라면 메모리는 0번부터 65535까지의 일련번호를 갖는다.[2] 프로그래머가 지정한 변수나 상수이름은 변환과정을 통해 메모리상의 특정 위치에 해당되는 일련번호와 1:1로 변환된다. 그래서 프로그래머가 변수에 어떤 값을 넣겠다는 코딩을 하면, 해당 값은 변수/상수 이름과 1:1로 연결되어 있는 메모리의 위치에 저장 된다. 마찬가지로 변수나 상수의 값을 읽어들이겠다고 요청하면 변수/상수 이름과 연결되어 있는 메모리 위치에서 데이터를 읽어들이게 되는 것이다.

다음의 그림은 실행되고 있는 프로그램이 메모리안에 어떻게 배열되는지를 그림으로 표시한 것이다:

 
메모리에 배치된 프로그램

위의 그림에서 메모리 주소는 위쪽이 작은 쪽이고, 아래쪽이 큰 쪽이다. 혹시 다른 그림에서 순서가 뒤집힌 걸 본 사람도 있을지 모르겠다. 그 경우에는 아래쪽이 메모리 주소가 작은 쪽이고 위쪽이 큰 쪽이다. 결국 같은 그림이니 오해 없길 바란다.

프로그램이 저장되는 메모리 위치가 일련번호 상으로 앞쪽이고, 그 다음에 배치되는 것이 앞에서 처음으로 코드를 입력해서 컴파일을 수행했던 Hello World 프로그램의 경우 printf 함수 내부에 있는 "Hello World!\n"에 해당되는 내용이 배치되는 스태틱 데이터 영역이다. 이 영역에는 이런 상수가 먼저 저장되고, 나중에 다시 설명할 스태틱 데이터가 저장 된다. 힙(heap)영역과 스택(stack)영역에 대해서는 나중에 다시 언급 하도록 하겠다.

프로그래머가 작성한 프로그램 코드 내의 모든 함수와 변수와 상수들은 위에 표시한 것과 같이 메모리 안 어디엔가 곱게 저장이 되어있고, 모든 것들에는 그에 상응하는 메모리의 일련번호가 존재한다. 그리고, 메모리의 일련번호를 주소(address)라 한다.


하드디스크의 경우 조금 더 복잡한 구조를 갖기는 하지만, 근원적으로 일련번호를 기반으로 한다는 데엔 변함이 없다. 하드 디스크를 액세스하는 운영체제의 일부분을 만드는 경우가 아니라면 하드디스크의 구조나 액세스 방법에 대해 관심을 가질 필요는 없을 것 이라고 본다. 대신에, 하드디스크에 저장되어 있는 파일을 읽거나 쓸 때에는 파일의 시작 위치로 부터 몇번째 바이트를 읽고 쓴다 라는 개념을 사용한다는 정도만 기억해 두면 되겠다. 이와 관련된 내용은 프로그래밍/C/기초 프로그래밍영역의 파일의 랜덤 액세스 부분에서 다루게 될 것이다.

이렇게 변수 혹은 상수가 메모리에 저장되어 있는데, 컴퓨터 입장에선 모두 다 동일한 데이터 이기 때문에 이 데이터를 어떻게 다룰지에 대해 프로그래머가 코드를 작성할 때 알려줘야 한다. 예를 들자면 int number라는 형태로 정의된 변수는 '정수이고 메모리 공간을 4바이트 차지하는 데이터를 처리 할 땐, 지정된 메모리에서 4바이트를 가져다가 계산을 할 땐 정수를 계산하는 방법으로 처리 한다.'는 것이다. 나중에 다시 언급 되겠지만, 그래서 C에는 동일한 정수를 다루는 데이터 타입들이 short int, int, long int, long long int, unsigned short int, unsigned int, unsigned long int, unsigned long long int 등의 여러 종류가 있는 것이다. 각각마다 메모리 공간을 차지하는 크기가 다르고, 부호가 있느냐 없느냐에 따라 연산하는 방법이 달라지기 때문에 종류별로 타입이 따로 있어야 하는 것이다.

이와같이 '변수' 혹은 '상수'라는 것은 메모리에 저장된 데이터 이고, 타입은 '변수'나 '상수'를 메모리에 저장하거나 꺼낼때, 그리고 연산할 때 어떻게 할 것인가를 컴파일러에게 알려주는 역할을 하고, '변수'나 '상수'가 저장될 위치는 메모리의 일련번호 -주소(address)를 가지고 지정하게 된다. 예를 들자면 '1264832 번 메모리 공간에서 시작하는 정수형 변수' 라던가, '7326892 번 메모리 공간에서 시작하는 부동 소수형 변수'라는 식으로 데이터의 위치가 지정된다. 문제는 프로그램 코드를 작성하는 사람이 이 메모리의 일련번호를 일일히 외워서 프로그램 코드를 작성할 수 없다는 것 이다. 그래서 고안된 것이 '심볼 테이블(symbol table)'이다. 다음은 그 심볼 테이블의 한 예이다.

Symbol Type Address
age int 63245744
height float 63245748

프로그램 코드를 작성하고 변수를 선언하면, 컴파일시에 컴파일러가 위와 같은 심볼 테이블을 만든다. 그리고 C 언어를 컴파일해서 기계어로 변환 할 때 심볼 테이블을 이용해서 변수에 해당하는 메모리 위치를 찾아 변수이름을 모두 메모리 위치로 변경해서 실행 파일을 만들게 된다.[3] 프로그램 코드를 작성할 때 변수를 선언 한다면, '이 변수의 이름은 마지막엔 메모리 위치에 해당되는 숫자로 바뀐다'라는 사실 정도는 기억해 두면 전체적인 C 언어를 이해하는데 도움이 될 것이다.

주석

+/-
  1. 바이트당 저장 비용은 하드 디스크가 가장 싸고, 그 다음이 메모리, 가장 비싼 저장 장치는 레지스터 입니다. (저장매체 / 용량)을 계산 하면 한 바이트 데이터를 저장하는데 드는 비용이 어느정도 되는지 계산이 될 것 입니다. 데이터를 처리하는 속도는 반대로 레지스터가 가장 빠르고, 그 다음이 메모리 마지막으로 하드디스크 순이 됩니다. 당연히 느린 장치에 저장되어 있는 데이터를 가능하면 빨리 사용할 수 있는 방법을 궁리하기 마련이고 많이 사용되는 방법중 하나가 캐쉬 입니다. 하드 디스크를 살때 18M 라는 등의 데이터가 있는 경우를 볼 수 있는데, 그 숫자가 하드디스크의 읽기/쓰기 속도를 향상하기 위해 마련된 캐쉬의 용량입니다. 데이터를 많이 읽고 쓰는 환경일 수 록 이 숫자가 클수록 더 빠르게 하드디스크가 동작 하겠지요. 마찬가지로 CPU에도 L1 캐쉬니 L2 캐쉬니 하는 설명이 붙어있는 것을 볼 수 있는데 이 캐쉬 역시 상대적으로 느린 램에서 빠른 CPU의 레지스터로 데이터를 보낼때 더 빨리 보내기 위해 읽어들이게 될 확률이 높다고 예상되는 데이터를 미리 옮겨두는 영역입니다.
  2. 메모리를 사용하는데 있어 몇가지 주의할 점이 있는데, 64K 메모리를 갖는 시스템이라고 해서 64K모두를 데이터를 저장하는 용도로 사용할 수 있는 것은 아닙니다. 일부 영역은 시스템을 관리하는데 필요한 기본적인 프로그램이나 읽기 전용이 데이터가 저장되어 있고, 이 영역은 보통 ROM 영역이라고 부릅니다. 또 다른 영역은 CPU와 램 이외의 외부장치(가장 쉽게 접할수 있는 외부장치의 예는 그래픽 카드입니다.)와의 통신을 위해 사용되는 경우도 있습니다. 이런 영역을 매핑(mapping)메모리라고 하는데, 해당 영역에 데이터를 써 넣거나, 데이터를 읽으면, 그 메모리 영역에 연결되어 있는 외부장치에 해당 데이터가 보내지거나, 그 외부장치가 관리하는 데이터를 읽어들이게 됩니다.
  3. 주: 기술적으로 볼 때, C 컴파일러가 바로 기계어로 바꾸는 것은 아니다. 실제로 C 컴파일러는 어셈블러로 변환을 하고, 변환이 끝난후 어셈블러가 기계어로 변환하는 작업을 하게 되는데, 실제 심볼 테이블을 생성하고 관리하는 쪽은 C 컴파일러 보다는 어셈블러 쪽이다. 또한 심볼 테이블에 저장되는 것은 변수나 상수 뿐 아니라 모든 식별자(identifier)가 저장된다.