Programming/C,C++

포인터(Pointer) 기본 정리

홍열 2012. 10. 28. 10:52
728x90

오늘은 C 언어의 꽃, Pointer에 대해서 정리해 보려고 합니다.


C언어를 처음 접하는 유저이면 한번은 거쳐야할 관문이며, 저 역시 배울때 어려움을 겪었습니다.


지금와서 공부해보면 별거 아닌거 같지만, 아직도 포인터는 헷갈리기는 마찬가지입니다.


다만, 한가지 조언을 하자면 포인터는 그림을 그려보면 쉽습니다!


1. 포인터의 개념 

: 포인터는 메모리 주소값을 저장하는 변수를 말합니다. 즉, 변수들의 물리적 주소를 저장합니다. 


2. 포인터 선언 및 연산


int *ptr_arr = NULL;


자료형 :  int

*연산자 :  *

변수 이름 : ptr_arr


여기서 잠깐!

포인터는 항상 NULL로 초기화 합니다. 포인터 변수는 메모리 주소에 직접 접근하기 때문에 프로그램의 안정성 차원에서 NULL로 값을 초기화 시켜주는게 좋습니다.


ex)

int value = 100;

int* ptr = NULL;

ptr = &value;


&연산은 주소 연산자로써 변수의 주소값을 얻을 때 사용됩니다. 


32비트에서의 포인트의 크기는 4bit입니다. 어떤 자료형이든지 포인터를 붙이면 4bit가 됩니다.


ex)

int *a = NULL;

float *b = NULL;


printf("%d %d", sizeof(a), sizeof(b));

결과는 4, 4가 나올 것입니다.


포인터 연산

(1) 주소 연산자 &

주소 연산자(&)는 선언에서 봤듯이, 변수의 주소 값을 얻을 때 사영됩니다. 

포인터 변수 = &주소


(2) 참조 연산자 *

참조 연산자는(*)는 포인터 변수에 저장된 주소를 이용하여 해당 주소에 있는 값을 나타낸다. 

*포인터 변수 = 값

ex)

char char_value_A = 'A';

char *ptr_char = &char_value_A;

결과는 ptr_char는 char_value_A의 주소를 가지고 있기 때문에 'A'라는 값을 가르킵니다.

char *ptr_char = &char_value_A; 이 문장을 다른 방법으로 써보면


char *ptr_char = NULL;

ptr_char = &char_value_A 라고 쓸수 있습니다. 


포인터 변수의 값을 변경하면 어떻게 될까요?

ex)

char char_value_A = 'A';

char *ptr_char = &char_value_A;

==============================
ptr_char = 'B';
라고 한다면 과연 어떠 어떠한 값들이 바뀔까요?

답은 ptr_char, char_value_A의 값이 모두 B로 바뀝니다. 
포인터란 그런 것입니다. 자신이 가르키고 있는 대상도 바뀌게 됩니다. 


3. 포인터를 이용한 동적 메모리 할당

포인터를 이용한 동적메모리 할당(malloc)를 많이 쓰이고, 많이 어려워 합니다. 
저도 처음에는 어려웠는데 이제 어느정도는 할 수 있을거 같아요!

동적 메모리 할당은 프로그램 실행중에 임의의 크기로 메모리를 할당 할 수 있다는 점에서 정적 메모리할당(Static)랑은 차이점을 보입니다.

ex)
정적 메모리 할당 : int int_arr[10];
동적 메모리 할당 : int *ptr_arr = NULL;
                          int size = 100;
                          ......
                          size = 200;
                          ptr_int = (int*)malloc(sizeof(int)*size);
위에 예문 2가지가 배열을 생성하는 예제입니다. 하지만 정적은 처음에 크기를 고정시켰고, 동적은 나중에 size를 바뀌어서 배열을 할당 했습니다. 

정적에서 메모리의 크기를 바꿔서 재할당하면 오류가 납니다.


정적은 사용하기 쉽고 해제또한 컴파일러에서 자동으로 해줍니다. 하지만, 사용하지 않는 공간에 대해서 낭비가 심합니다. 

동적은  사용하기 어렵지만, 필요한 양만큼만 쓸수가 있습니다. 다만, 다 쓰고 난후에는 사용자가 항상 메모리 해제(free)를 해줘야합니다.


그럼 이제 동적으로 생성하는 방법을 알아 보겠습니다.

ex)

int * ptr_arr = NULL;

int size = 100;

ptr_arr = (int*)malloc(sizeof(int)* size);

if(ptr_arr != NULL)

{     

memset(ptr_arr, 0, sizeof(int)*size);

}

free(ptr_arr);

================================

먼저, *ptr_arr를 선언하고 malloc를 통해서 메모리를 할당해줍니다.

malloc는 매걔변수로 size를 받으며 반환값은 NULL 혹은 메모리의 첫번째 주소를 반환하게 되어 있습니다.

다음 줄을 통해 메모리 할당 여부를 검사하고, memset함수를 통해 메모리가 할당 되면 쓰레기값으로 채워져 있는 ptr_arr를 0으로 초기화 했습니다. 

다 쓰고난 메모리는 free를 통해서 해제해 주었구요.


여기서 malloc(size_t) 에서 매걔변수에서 의문점이 생깁니다

정수형으로 초기화 하면 되지 왜 sizeof를 통해서 했느냐...

이유는 간단합니다. 포인터는 4bit입니다. 즉, 하나의 메모리가 4bit를 차지합니다.

그렇게 되면 배열 100개를 생성하려면 sizeof(int)*100; 즉 400이라는 공간이 할당되고, 400/4 int형 크기인 4로 나누면 100개가 할당 된 것을 볼 수 있습니다. 


4. 포인터의 포인터 

'포인터의 포인터'는 포인터 변수를 가리키는 포인터 변수를 말합니다. 

ex)

int int_value = 500;

int *ptr_int = &int_value;

int **pptr_int = &ptr_int;

최종적으로 더블포인터는 싱글 포인터 주소를 가지고 있습니다. 

이러한 더블포인터가 자주 사용되는 것은 ' 2차원 배열 ' 입니다.

ex)

int row = 3, col =4;


int**pptr_int_array = NULL;

pptr_int_array = (int**)malloc(sizof(int*) * row);


처음에는 행에 대한 할당을 해줍니다. 이렇게 함으로써 3개의 행에 대한 할당이 완료 되었습니다.

이제 각 행에 대한 열을 할당해주어야겠군요.

 for(i = 0; i<col; i++)

{

pptr_int_array = (int*)malloc(sizeof(int)*col);

memset(pptr_int_array[i], 0, sizeof(int)*col);

}

열에 대한 할당을 하고, 메모리를 0으로 채웠습니다.


결과는 

0 0 0 0

0 0 0 0

0 0 0 0 

입니다. 

그럼 더블 포인터 접근은 어떻게 할까요?


**ptr_int_array = 1;

위 문장은 ptr_int_array[0][0] = 1; 과 같은 문장입니다. 포인터는 첫 주소값을 가지고 있기 때문이죠 

그러면 ptr_int_array[2][3]을 포인터 방식으로 접근하면 어떻게 해야 할까요?


*(*(ptr_int_array+2)+3) 이라고 쓰시면 됩니다.

궂이 해석하자면 ptr_int_array에 2를 더한값의 위치를 참조하고, 거기에 3을 더한값의 위치를 참조하라 입니다. 


5. 기타 

구조체에서도 포인터를 쓸수 있습니다

struct student student_lee;

struct student* ptr_student = NULL;

ptr_student = student_lee;


 다만 다른점이 그냥 구조체 선언시에는 .  으로 접근이 가능합니다.

포인터 구조체를 접근하려면 방법이 약간 다릅니다.


원래 구조체의 값을 접근하려면 참조 연산자를 써야합니다. 그러므로

(*ptr_student).멤버변수로 접근해야합니다. 하지만 사람들이 사용하면서 불편함을 느껴 다음과 같은 방법을 만들었습니다.

ptr_student->멤버변수 

화살표 연산자를 사용하므로써 쉽게 접근이 가능합니다.


이것으로써 포인터에 대한 기본적인 정리를 마칠께요~