본문 바로가기

데이터구조

데이터구조 9차시: 포인터, malloc

반응형

 


Pointer (포인터)

C언어 프로그래밍 공부를 힘들게 하는 원인 중의 하나다.

개념은 간단하다.

포인터는 주소를 다룬다.

주소란 무엇인가? 

예를 들어보자.

int a;

a = 4;

Integer(정수)형 변수 a를 만들고, 거기에다가 값 4를 저장한다.

이는 변수a를 위해 메모리에 저장공간을 만들고, 

공간 'a'에 값 4를 저장한 것이다.

메모리의 저장공간은 모두 고유의 숫자주소를 가지고 있다.

사람들이 고유의 숫자 주민번호를 가지고 있듯이.


프로그래밍할 때 저장공간을 쉽게 기억하기 위해 'a'라는 이름을 쓰지만

사실은 'a'도 숫자 주소를 가지고 있다.

'a'의 주소를 알고 싶다고?

printf ("%d", &a);

&a는 a와 매칭된 메모리 저장공간의 주소를 의미한다.

어느 변수든지 기호 &를 앞에 붙이면 해당 저장공간의 주소가 된다.


주소를 다루기 위해 포인터라는 것을 만들었다.

포인터는 주소를 저장할 수 있도록 한다.

int *b;

포인터를 만드는 방법은 변수이름 앞에 기호 *를 붙이면 된다.

이제 변수 b에 저장되는 값은 주소로 인식된다.

100을 저장한다. 주소 100번지가 된다.

1000을 저장한다. 주소 1000번지가 된다.


b = &a;

b에 변수 a에 매칭된 저장공간의 주소를 저장하는 것이다.


왜? 굳이 포인터라는 걸 만들었나?

포인터를 통해 복잡한 일을 쉽게 하기 위해서다.

어떤 복잡한 일인가?

'주소 000번지에 가서, 그 번지에 저장되어 있는 값을 읽거나 쓰는 일이다.'


b = 100; 

변수 b는 주소 100번지를 저장한다.


printf ("%d", *b);

변수 b가 저장한 값 100을 주소로 인식하고, 그 100번지에 저장되어 있는 값을 읽어오는 것이다.

포인터 변수 앞에 기호 *를 쓰면, 그 번지에 저장된 값을 읽거나 쓸 수 있다.


단, 헷갈리지 말자.

int *b; 이건 변수 b가 포인터라는 것을 정의하는 것이다. 

포인터 변수를 만들 때도 기호 *를 사용한다.


*b = 10;

변수 b가 저장한 값 100을 주소로 인식하고, 그 100번지에 값 10을 쓰는 것이다.

기호 *을 붙여 값을 쓰는 용도로 사용한 것이다.


배운 것을 정리해보자.

1. a = 10;

2. b = &a;

3. *b = 20;

4. printf ("%d", a);


라인 1. 변수 a에 값 10을 저장한다.

라인 2. 변수 a에 매칭된 저장공간의 주소를 b에 저장한다.

라인 3. b에 저장된 주소는 a것이므로, a에다가 20을 저장한다.

라인 4. 위에서 a에 저장된 값이 20으로 바뀌었으므로 10이 아닌, 20을 출력한다.


포인터는 어디에 필요한가?

컴퓨터를 생각해보자.

키보드, 디스크, 비디오카드, USB메모리, CPU 등 여러 가지 장치들이 있다.

프로그램으로 이 장치 들에게 명령을 내리고 싶다면, 어떻게 할 수 있을까?

각 장치들은 고유의 주소를 가지고 있다.

이 주소들을 이용하여 명령을 내릴 수 있다.

예를 들어, 비디오카드가 100번지에 있다면, 100번지에 값을 읽거나, 써서 비디오카드에게 명령한다.

이를 가능하게 하는 것이 포인터다.

따라서 포인터를 아는 것은 시스템을 내 휘하에 두고 이러저러 명령을 내릴 수 있는 힘을 얻는 것이나 마찬가지다. 


int *a

포인터 변수를 선언한 것이다.

하나하나 뜯어서 살펴보자.

a는 포인터 변수의 이름이다.

*: a 앞에 붙은 기호 *은 변수 a가 포인터임을 나타낸다. 기호 *이 없으면 포인터가 아니다.

int: 이것의 의미를 잘 이해해야 한다. 포인터 변수 a를 이용하면 어떤 주소 위치에 값을 쓸 수도 있고, 읽어올 수도 있다. 이 때 그 값의 형(type)을 나타낸다. 즉 변수 a를 이용하면 읽거나 쓰는 값은 int이고, 따라서 4바이트 단위로 읽거나 쓸 수 있게 된다. (32비트 운영체제일 경우에 말이다. 64비트 운영체제이고 int가 64비트이면 8 바이트 단위로 동작한다.)


int **a

포인터 변수이긴 하다.

여기에는 기호 *이 두 개가 붙었다.

이중 포인터라는 것이다.

이것의 의미도 하나하나 뜯어보자.

a: 포인터 변수의 이름이다.

* (오른쪽에서 첫 번째): 변수 a가 포인터임을 나타낸다.

int * (오른쪽에서 두 번째 *): 변수 a를 이용하여 읽고 쓸 수 있는 값의 데이터 형이 int *, 즉 정수 포인터

 

예제프로그램


변수 a를 만들고, 거기에다가 10을 넣었다.

포인터 변수 b를 선언했다.

포인터 변수 c를 선언했다.

b를 이용해서는 int값을 읽고 쓸 수 있으므로, b에는 변수 a의 주소를 넣는다.

c를 이용해서는 int *값을 읽고 쓰기 때문에, c에는 int 포인터 변수 b의 주소를 넣는다.

출력결과는 모두 10, 10, 10

첫 번째 10: 변수 a의 값을 출력한 것


두 번째 10: 포인터 변수 b를 이용하여, a의 주소를 이용해서 int값을 읽어서 출력했다.

세 번째 10: 이중 포인터 변수 c를 이용했다. **c == *(*c)로 이해하면 쉽다. *c는 b에 저장된 a의 주소를 읽는다. *(*c)는 a의 주소를 이용하여 a에 저장된 값을 읽는다.


malloc

메모리를 할당받는 데 사용하는 함수이다.

원하는 크기의 메모리 공간을 할당받을 수 있다.

이 함수의 인자는 할당받기를 원하는 크기이다.

크기는 바이트 단위이다.

malloc(100) : 100 바이트 크기의 공간할당을 요청하는 것이다.

이 함수의 반환값은 주소이다.

할당된 공간의 시작주소이다.

반환값이 주소이므로 이 주소는 포인터 변수에 저장해야 한다.



malloc casting

malloc함수의 원형은 아래와 같이 생겼다.

 void * malloc ( int size)

반환형이 독특하게 생겼다.

void *

생긴 것을 봐서는 포인터이다. 

왜냐면 *이 붙었으니까.

이것으로부터 유추해보면, 포인터 즉 주소값을 반환한다는 뜻이다.

좀 더 추리해보면, 할당받은 공간과 관련된 주소일 것이다.

정확하게 말하자면, 할당받은 공간의 시작주소가 반환값이 된다.

그러면 void는 무슨 뜻인가?

void의 사전적인 의미는 '비어있는' '없는' '무효' 이다.

데이터 형이 없다는 뜻으로 생각하면 된다.


왜 void, 데이터형을 없는 것으로 해 놨을까?

그건 공간을 마음대로 활용할 수 있도록 해 주기 위해서이다.

공간에 int 값을 쓰거나 읽을 용도로 할당받는 경우도 있고,

char 값 용도로 쓸때도 있다.

그러니 데이터형을 일단 없다고 하고, 

실제로 할당받는 순간에 그 형을 정해서 쓰기 위해서 void로 해 놓은 것이다.


공간을 할당받는 순간에 데이터형을 결정하다. casting

int *k;

k = (int *)malloc(100);

char *m;

m = (char *)malloc(100);

둘 다 100 바이트 공간을 할당받는다.

하지만 위의 것은 int형 값들을 읽고 쓰는 공간으로 만들기 위해 (int *)로 캐스팅한다.

아래는 char형 값을 위한 것이다.


할당받은 공간에 값을 저장해보자.

*k = 1;

값 1을 쓰는 것이다.

int형 1을 쓰는 것이다.

int형은 4바이트이므로,

할당받은 공간 100바이트 중 4바이트에다가 1을 쓰는 것이다.

*m = 1;

역시 값 1을 쓰는 것이다.

하지만 이번에는 char형 1을 쓰는 것이다.

char형은 1바이트이므로,

1바이트에다가 1을 쓰는 것이다.


free

컴퓨터의 메모리공간은 유한한다.

malloc으로 할당받은 공간을 다 썼으면 반환해야 한다.

이 때 사용하는 함수가 free이다.

이 함수는 인자로 반환하고자 하는 공간의 시작주소를 받는다.

malloc했을 때 할당받은 공간의 시작주소를 얻을 수 있으니,

이것을 잘 간직해 두었다고,

free할 때 쓰면된다.

free(k);

free(m);

k와 m은 각각 할당받은 공간의 시작주소를 가지고 있으니,

이렇게 하면 되는 것이다.


free를 안하면 어떻게 될까?

메모리 공간이 모자르니 다른 프로그램이 손해를 떠안아야 한다.

프로그램이 아예 실행이 안 될 수도 있고,

실행되는 도중에 '더 이상 메모리를 얻을 수 없습니다.'하고,

중간에 종료되기도 한다.

그러니 malloc으로 할당받은 공간 중에 필요없게 된 것들은

그 때 그 때 free해서 반환하는 것이 에티켓이다.


프로그램 종료는 자동 free된다.

프로그램이 끝나면,

수행되면서 할당받았던 공간들이 자동 반환된다.

그러니, 종료되고 나서, '아차, free 안했네' 걱정할 필요없다. 




반응형