티스토리 뷰

심화

포인터(Pointer) - 개념

Just4Fun 2016. 7. 2. 22:22

C언어가 처음 만들어진 때가 1970년대 초반이므로 지금으로부터 40년도 더 되었다.  

그 당시에 사용되었던 많은 프로그래밍 언어들이 거의 사라졌고, 끊임 없이 새로운 프로그래밍 언어들이 생겨나고 있지만, C언어는 아직까지도 가장 많은 프로그래머가 사용하는 언어중 하나이다.  특히 임베디드 시스템 소프트웨어 개발에는 거의 절대적이라고 할 수 있을 정도로 C언어가 사용되고 있다.  이렇게 오랜 시간동안 다양한 분야에서 C언어가 사용되는 이유는 여러가지가 있겠지만 그 중에서도 가장 중요한 이유는 아마도 포인터 때문일 것이다.

어느정도 C로 프로그램을 구현해 본 경험이 있는 프로그래머라면 포인터가 무엇인지 이미 잘 알고 있을 것이다.  그러나 실제로 포인터를 효과적으로 적절히 사용하는 것은 생각처럼 쉽지 않다.  그 이유는 아마 포인터에 대한 개념을 확실하게 이해하지 못하기 때문일 것이다.

이번 글에서는 포인터가 무엇인지 가급적 개념 위주로 쉽게 설명해 보도록 하겠다.


먼저 아래 사진에 나오는 물건을 본적이 있는지?

시력이 나빠서 안과에 가서 진찰을 받았거나 안경점에서 안경을 맞추어 본 경험이 있다면 무엇인지 알수 있을것이다.

안경을 맞추기 위하여 안경점에 가면 시력 측정 기계를 이용하여 어느정도로 눈이 나쁜지 알아낸다.  그 다음에 기계로 알아낸 시력값에 맞는 렌즈를 위의 사진처럼 프레임에 끼우고 실제 눈으로 봤을때 가까운곳에서부터 멀리있는곳까지 선명하게 보이는지 알아본다.  만약 사물이 선명하게 보이지 않는다면 굴절률이 조금 다른 렌즈를 끼워 가며 눈에 가장 잘맞는 렌즈를 찾아낸다.

만약 렌즈 프레임에 위의 그림에서 보이는것처럼 빨간색 렌즈를 끼우고 세상을 보면 어떻게 될까?  세상이 온통 빨갛게 보일것이다.  초록색 렌즈를 끼우고 세상을 보면 이번엔 온통 초록색 세상이 보일것이다.

자, 이제 다음과 같은 상황을 머리속에서 상상해 보자.

의사가 별모양의 렌즈를 프레임에 끼워 주었다.  환자는 의사가 어디를 바라보라고 말을 해주지 않았기 때문에 아무곳이나 쳐다보고 있을것이다.  그랬더니 거기에는 별모양의 물체가 보였다.  고개를 조금 움직여 보았더니 그 옆에 새로운 별이 하나 보였다.  머리를 움직일때마다 새로운 별모양이 하나씩 생겨났다.  이때 의사 선생님이 창문 너머 건너편에 있는 빌딩 꼭대기를 바라보라고 특정 위치를 알려주었다.   의사가 가리키는 곳을 바라보았더니 역시 건물 꼭대기에 별모양이 보였다.  이 상태를 유지한 상황에서 이번엔 별모양의 렌즈를 빼고, 삼각형 모양의 렌즈를 프레임에 바꿔 끼었다.  그랬더니 좀전엔 별모양으로 보이던 건물꼭대기가 이제는 삼각형으로 보이게 되었다.  의사가 건물 꼭대기에서 한층 아래를 보라고 하자 거기엔 새로운 삼각형 모양이 보였다.  의사가 삼각형 모양의 렌즈를 빼고 아무 모양과 색이 없는 판유리를 프레임에 끼웠다.  이번엔 당연히 아무런 모양이 나타나지 않게 되었다.  모양이 없으므로 옆이나 아래위에도 당연히 아무 모양이 안나타나게 되었다.


위의 가상 상황을 C에서의 포인터로 바꾸어서 생각해 보자.

C에서 포인터를 사용하기 위해서는 다음과 같이 정의하여야 한다.

데이터 타잎 *포인터변수명;

데이터 타잎은 곧 위에서 얘기했던 렌즈의 종류에 해당된다.  포인터 변수명은 렌즈 프레임에 해당된다.  렌즈 종류에 무관하게 렌즈 프레임의 크기가 항상 똑같은 것과 마찬가지로 포인터변수의 크기는 항상 일정하다.  포인터 변수는 주소값을 저장하므로 unsigned int로 선언한 것과 같다고 보면 된다.

void *v_ptr;
char *c_ptr;
int *i_ptr;
struct tm *t_ptr;

위의 코드에 나오는 v_ptr, c_ptr, i_ptr, t_ptr의 크기는 모두 동일하다.  8비트 AVR의 경우 2바이트 크기를 가진다.(int 형이 2바이트 크기를 가지므로).

단지 포인터가 가리키는 메모리의 크기와 모양이 모두 다르게 보일뿐이다.  메모리에 저장된 값은 전혀 바뀌지 않는 상태에서 모양만 다르게 보인다는 것에 특히 주의하기 바란다.

void 형으로 포인터를 선언하면 아무 모양이 없는 판유리렌즈를 프레임에 끼우는것과 같다.

char로 포인터를 선언하면 그 포인터가 가르키는 곳의 모양이 모두 char로 보이는 것이다. 당연히 int형으로 포인터를 선언하면 포인터가 가르키는 메모리가 모두 int형이 된다.

위의 코드 마지막 줄에 나오는 t_ptr이 가리키는 주소의 메모리를 바라보면, 그곳에는 다음과 같은 모양의 메모리가 보이게 된다.

struct tm {
  int tm_sec;   /* Seconds */
  int tm_min;   /* Minutes */
  int tm_hour;  /* Hour (0--23) */
  int tm_mday;  /* Day of month (1--31) */
  int tm_mon;   /* Month (0--11) */
  int tm_year;  /* Year (calendar year minus 1900) */
  int tm_wday;  /* Weekday (0--6; Sunday = 0) */
  int tm_yday;  /* Day of year (0--365) */
  int tm_isdst; /* 0 if daylight savings time is not in effect) */
};


이제 포인터 변수에 특정 메모리 위치 주소를 지정해 보도록 하자.

void *v_ptr = (void *)0x1000;
char *c_ptr = (char *)0x1000;
int *i_ptr = (char *)0x1000;
struct tm *t_ptr = (void *)0x1000;

위의 코드는 모든 포인터 변수에 같은 메모리 주소를 할당하는 코드이다.  모두 같은 0x1000번지를 바라보고 있지만, 보이는 모양이 제각각 다르게 보이게 된다.  t_ptr에 주소를 할당하기 위하여 (void *)를 사용하였는데, 이렇게 해도 전혀 문제없다.  0x1000이라는것이 포인터 주소값이라는것만 컴파일러에게 알려주면 되기 때문이다.

위의 코드는 다음과 같이 정의하여도 된다.

void *v_ptr = (void *)0x1000;
char *c_ptr = (char *)v_ptr;
int *i_ptr = (char *)v_ptr;
struct tm *t_ptr = (void *)v_ptr;

v_ptr변수에 0x1000이라는 주소값이 할당되고, 나머지 포인터 변수들은 v_ptr가 가지고 있는 주소를 받아서 할당되므로 결국 모든 포인터 변수에는 같은 메모리 주소값을 가지게 되는 것이다.

엔디안(Endian)에서 설명한 글에 다음과 같은 코드가 나온다.

    uint32_t    data;
    uint32_t    *p_32;
    uint16_t    *p_16;
    uint8_t     *p_8;
 
    data = 0x12345678;
    p_32 = &data;
    p_16 = (uint16_t*)&data;
    p_8  = (uint8_t*)&data;

data라는 이름의  4바이트 변수를 선언한 다음 0x12345678의 값을 할당 하였다.  data변수는 메모리 어딘가에 위치할 것이고, 그 주소를 p_32, p_16, p_8이라는 포인터 변수 모두에 넣어 주었다.  각각의 포인터 변수에 들어가 있는 메모리 주소와, 그 주소가 가리키고 있는 메모리에 들어있는 값을 출력하면 다음과 같은 결과를 얻을수 있다.

각 포인터 변수에 들어간 메모리 주소는 모두 0x8F0으로 동일한 것을 볼 수 있다.  단지 포인터 변수의 데이터 타잎이 다르기 때문에 출력되는 메모리 값의 크기가 다르게 나오는것을 확인 할 수 있다.


그렇다면 다음과 같은 포인터 변수는 어떤 의미일까?

char **argv;

위의 코드를 렌즈 프레임으로 비유하여 설명하면 다음과 같다.  argv라는 이름의 렌즈 프레임을 통하여 어느 곳을 바라 보면 거기에는 또다른 렌즈 프레임들이 연속해서 보이게 된다.  당연히 그 크기는 렌즈 프레임만큼의 크기가 될것이며,  거기에 있는 렌즈 프레임을 선택하여 세상을 바라보면 char 모양의 데이터들이 보이게 된다.

위 그림에서 보는것처럼 argv가 가리키고 있는 메모리는 포인터주소가 연속해서 들어있는 배열과 같은 모양을 하고 있다.  따라서 다음과 같이 표현할 수도 있다.

char *argv[];

즉, 포인터 변수들의 배열이라는 뜻이다.

마지막으로 다음과 같은 포인터 변수도 있다.

int (*callback)(int, char*);

callback이라는 포인터 변수는 데이터가 있는 메모리 주소를 가리키는것이 아니고 함수의 주소를 저장한다.  그 함수의 모양은 return 값이 int 형이고, int와 char *를 입력으로 받는 함수여야 한다.

예를 들어 다음과 같이 코드를 만들면 function pointer라는 문자열을 화면에 두번 출력하게 된다.

int foo(int num, char *name) { int i; for (i=0; i<num; i++) printf(name); return i; } callback = foo; callback(2,"function pointer");


결론적으로 C에서의 포인터는 시험용 렌즈 프레임과 같다고 보면 된다.  그 프레임에 어떤 렌즈를 끼우고 어디를 바라보아야 하는지를 알려주는 것이 전부다.   


심화 과정 목차


'심화' 카테고리의 다른 글

패킷 통신 - 회로 구성  (0) 2016.07.30
패킷 통신 - Serial Packet Communication  (0) 2016.07.30
random 함수  (4) 2016.06.12
EABI(Embedded Application Binary Interface)  (0) 2016.04.23
엔디안(Endian)  (0) 2016.04.23
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함