[C] 포인터란?

언어/C 2018. 12. 17. 06:26

C언어 학습자들을 가로막고 있는 가장 큰 장벽은 아마 포인터일 것이다.

사실 나는 그렇게 어렵다고 느껴본 적은 없는데... 그렇다면 그런거겠지

정말 이해만 하면 별거아닌 개념이다.


변수가 무엇인지 기억하는가?

변수를 생성한다는 것은 특정 메모리공간에 이름을 붙여준다는 뜻이다.

그리고 포인터는 그 특정 메모리공간의 진짜 이름을 가리키는 값이다.

비유하자면 변수는 별명이고, 포인터는 본명이라고 할 수 있겠다.

별명은 부르기에 따라서 수십수백개도 될 수 있지만 본명은 하나 뿐이다.(귀화를 하지 않았다면)


그래서 이걸 도대체 어떻게 쓴다는 건가?

이젠 방법론적인 측면으로 봐보자.

일단 포인터 변수는 다음과 같이 사용할 수 있다.

int num=20;
int* ptr=#
printf("%d",*ptr);
//output->20

int* 는 "int 타입을 가리키는 포인터 타입" 이라고 이해할 수 있다.
마찬가지로 char*, short*... 뭐든 *를 갖다붙이면 그 타입을 가리키는 포인터 타입이 된다.

그래서 int* 타입의 변수 ptr가 생성된다.

그리고 ptr도 변수니까 값을 넣을 수 있다!

10이나 50같은 그냥 숫자도 넣을 수 있긴 있는데, 그래봐야 쓰지도 못하는 쓰레기가 될 뿐이다.

포인터 변수니까 당연히 그에 맞는 포인터 값(이하 주소값)을 넣어줘야 한다.

그래서 등장하는 부분이 바로 &num이다.
num 변수의 주소값을 반환한다는 의미이고, 이것이 ptr에 대입됨으로써 ptr은 진정한 포인터의 역할을 할 수가 있게 된다...

이제 ptr은 num의 본명(주소값)을 갖고 있다.
이 본명을 굳이 알고싶다면

printf("%p",ptr); 이렇게 쓰면 된다. 숫자와 알파벳으로 얼룩진 16진수 숫자가 나올 것이다...

그리고 *ptr; 이라는 연산을 행해보자.
여기에서의 *은 곱셈도 아니고, int* 타입에서의 *도 아닌 별개의 연산이다. 그냥 기호만 같다.

저 구절을 해석하자면 "ptr에 담긴 본명(주소값)을 따라가서 거기에 저장된 값에 접근하겠다"

라는 의미가 된다.

그리고 
*ptr=40;
이런식으로 접근해서 넣어주면

본명을 통해서 본질적인 접근을 한 것이기 때문에 당연히 num의 값도 40이 된다. (num은 별명일 뿐이다.)


그리고 특정 구간의 로컬 값을 외부에서 컨트롤할수 있도록 넘겨줄 수도 있다.


int 타입 변수 num을 함수에 넘겨서 하나 증가시키는 코드를 짜보자.

void AddOne(int add)
{
ㅤadd++;
}

int main()
{
 ㅤint num=10;
ㅤAddOne(num);
ㅤprintf("%d",num);
}

자 컴파일을 해보자.

무슨 값이 나올까?

우리가 기대하는 값은 11인데

결과는 10이다.

왜 그럴까?

매개변수로의 값 전달은 오로지 '값' 전달만 가능하기 때문이다.

num에 담긴 값은 10이고, AddOne 함수의 인자로는 10이 전달된다. num이 아니라.

AddOne 안에서 그냥 상수 10을 가져다가 add 변수에 값을 대입해넣는데,

그걸 0으로 만들든 35로 만들든 num과 도대체 무슨 상관이겠는가?

이럴 경우에 포인터가 사용된다.

포인터를 적용해서 고쳐보자.

void AddOne(int* add)
{
ㅤ*add++;
}

int main()
{
 ㅤint num=10;
ㅤAddOne(&num);
ㅤprintf("%d",num);
}

됐다. 이젠 정상적으로 11이 출력된다.

AddOne에 num의 주소를 전달하고,
AddOne 안에서는 그 주소를 따라서 num의 본체에 접근했기 때문이다.


그리고 또 하나의 용도가 있다.

int나 double같은 건 뭐 4바이트 8바이트밖에 안되니까 다른 함수에 전달할때 그냥 그대로 전해주면 된다.

그런데 구조체같은 경우라면 어떨까?
이런저런 정보들이 많이 들어가다보면 수십바이트를 차지할 수도 있고, 복사 시간도 더 잡아먹게 된다.

포인터의 크기는 보통 4바이트이기 때문에 구조체 대신 구조체의 포인터를 전달하면 효율을 향상시킬 수 있다.


그리고 포인터 중에서도 이질적인 포인터가 있다.

바로 void* 타입이다.

이 타입은 어떤 포인터 타입이든 다 저장할 수 있다.

그대신 참조 접근 { ex)*ptr } 이 불가능하다.

그래도 뭐 *(int*)ptr 이런식으로 편법을 쓰면 되고, 보통 포인터 타입을 확정할 수 없을 때 쓴다.

예로, 메모리를 동적할당해주는 alloc 계열 함수(malloc, calloc...)들은 모두 void*로 값을 반환한다. 그래서 int* ptr=(int*)malloc(sizeof(int)); 같은 식으로 사용하는 것이다.
alloc에 넣을 타입이 뭔줄알고 int*, char*같은걸 반환하겠는가...


포인터에 대한 설명은 이게 다다.

별거 없다.

설정

트랙백

댓글