본문 바로가기

C/개념

포인터(Pointer) .. 왜 쓸까요? (1)

반응형
SMALL

C 언어의 꽃 포인터(Pointer)입니다. 대학 시절 때도 포인터에 막혀서 제 학점을 후루룩 말아 잡쉈는데, 이제 피할 수 없는 시기가 와서 한번 정리를 하려고 합니다.


포인터는 왜 쓸까요?

포인터 변수는 변수의 주솟값을 받는 변수인데 과연 어디에 쓰려고 하는걸까요? 포인터 변수를 사용하는 이유를 알려면 주솟값의 존재 이유를 알면 됩니다.

우리는 인터넷에서 쇼핑을 하면 배송지에 꼭 집의 주소를 적습니다. 그 곳으로 물건을 배달하지요. 이렇듯 주소는 특정 위치를 정확히 알려주는 지표입니다.

컴퓨터에서도 주솟값은 방대한 메모리에서 해당 변수가 저장되어 있는 위치를 정확히 알려주는 역할을 합니다.

즉, 포인터 변수는 저장된 주솟값을 통해 언제든지 해당 변수에 접근할 수 있게 됩니다. 물론 수정도 가능하죠.

 

근데 여기서 의문?

포인터 변수의 기능이 담겨 있던 주솟값을 통해 해당 변수에 접근하는 것이 전부이면, 굳이 포인터 변수를 사용하지 않아도, 원래 있던 변수를 계속 사용하면 되지 않을까요? 사실 그냥 변수는 메모리를 컴퓨터가 관리합니다. 대신 포인터로 관리하는 변수는 개발자가 직접 메모리를 관리할 수 있습니다.

 

포인터를 사용함으로서 얻는 장점은 상당히 많습니다.

단순히 주솟값을 통해 해당 변수로 접근하는 기능이 응용할 곳이 매우 많다는 의미입니다.

 

  1. 메모리에 직접 접근이 가능
  2. 구조화된 자료를 만들어 효율적 운영이 가능
  3. Call by reference 방식 이용 가능
  4. 배열, 구조체 등의 복잡한 자료 구조와 함수에 쉽게 접근
  5. 메모리 동적 할당이 가능

이런 장점들이 있는데, 더 있습니다.


C언어 같은 경우는 저급 언어(Low-Level Language)의 특성을 띄고 있습니다. 왜냐하면 포인터를 이용하여 메모리에 직접 접근이 가능하기 때문입니다.


저급 언어(Low-Level Language)는 사용자보다 컴퓨터를 중심으로 개발한 언어입니다. (예 : C / C++)

반대로 고급 언어(High-Level Language)가 있는데 이는 사용자를 위한 언어입니다. (예: Python / JAVA)


우선 가볍게 넘어가면 포인터는 메모리 주소 '값' 입니다. 그렇기에 포인터 변수는 주솟값을 저장합니다. 그렇기에 메모리에 변수들이 어떻게 저장되는지가 중요한데, 그렇기 때문에 자료형의 개념을 제대로 알고가야 합니다.

 

C에서는 다음의 기본 자료형을 제공합니다. (크기 단위는 byte입니다)

종류 이름 크기
정수형 signed / unsigned char 1
short 2
int 4 (int형이 2^32 - 1까지 할 수 있는 이유)
long 4 / 8
long long 8
실수형(부동소수) float 4
double 8

long 형은 32비트 운영체제에서 4 바이트, 64비트 운영체제에서 8 바이트로 동작합니다. 또한 실수형에서 long double 형이란것도 존재합니다.

 

여기서 중요한 것은, 모든 자료형이 일정한 크기를 가지고 있다는 것입니다. 이것은 구조체도, 배열도 마찬가집니다. 모든 자료형에 크기가 존재한다는 것은 자료형을 사용하는 변수는 메모리의 어딘가에서 그 변수의 자료형에 해당하는 크기만큼을 사용합니다.

변수의 메모리 주소를 확인하는 방법은 변수명 앞에 & 기호를 붙이면 되고, 변수의 크기를 확인하는 방법은 sizeof 연산자를 이용하면 됩니다.

// 변수의 자료형과 자료형의 크기를 알아봅시다.

char a; // char형, 1바이트
int b; // int형, 4바이트
double c; // double형, 8바이트

char d[10]; // char [10]형, 10바이트 => char 형이 10개니까
int e[5][10]; // int[5][10]형 200바이트 => 4 * 5 * 10

a, b, c는 앞에 나와있는대로 자료형의 종류와 크기를 적으면 됩니다. 근데 d와 e는 char형, int형의 배열입니다. 여기서 알 수 있듯 배열 또한 자료형입니다. 그리고 배열의 크기가 다르면 다른 배열입니다.

예를들어 char[10] 자료형과 char[11] 자료형은 엄연히 다른 자료형입니다. 전자는 10바이트 크기의 자료형이고, 후자는 11바이트 크기의 자료형이죠.

물론 크기가 같아도 적힌 숫자가 다르면 다른 자료형입니다. (ex, char[5][10], char[10][5])

여기까지가 자료형에 대한 설명입니다. 포인터를 알아보기 전에 위 내용을 모두 숙지하도록 합시다.


포인터는 메모리 주소를 저장하고, 특정 자료형으로 그 주소에 접근하기 위한 자료형입니다. 이것이 의미하는 것은, 포인터 변수에는 메모리 주소를 담을 수 있는 크기와, 그 주소를 접근하기 위한 자료형이 필요하단 것입니다.

 

실제로 포인터 변수는 항상 4 / 8 바이트의 정해진 크기를 갖습니다. 여기서 4 / 8 바이트는 long형과 마찬가지로 32비트 운영체제에서 4바이트, 64비트 운영체제에서 8바이트를 의미합니다.

 

포인터 변수를 선언하는 방법은 아래와 같습니다.

// 자료형 *변수명;
char *pa;
int *pb;
double *pc;

int a = 0;
pb = &a;

변수 pb는 int * 자료형이며, 크기는 메모리 주소를 담기 때문에 8바이트입니다. pb는 값으로 변수 a의 주소를 가지고 있습니다.

포인터 변수가 가지고 있는 주소에 접근하는 방법은, 변수 앞에 *를 붙이는 것입니다.

*pb = 3;
printf("%d %d", a, *pb); // 3 3 출력
sizeof(pb) // 포인터 변수이므로 8바이트
sizeof(*pb); // int형으로 접근하였으므로 4바이트

이해가 되나요?? 안되죠??

더 쉽게 설명을 드리자면 '*'는 값입니다. '&'는 주소값입니다.

그렇기에 위에서 pb의 값을 뽑아내고 싶었기에 *pb로 값을 뽑아냈습니다. 그렇다면 '*'을 빼면 어떻게 될까요?? 포인터 변수에 a의 주솟값을 넣었기 때문에 a의 주솟값이 들어가게됩니다. 그래서 8바이트로 출력되는 것입니다.

'&'가 붙으면 무조건 주소값이 됩니다. 포인터 변수는 무조건 주솟값을 담아야 합니다. 따라서 특수한 기호 &를 붙여줘야 합니다. 아래 그림을 보도록 하겠습니다.

이 그림에서 생각해야 할 점은 왜 &ptr = 0xB일까요?? &기호는 주솟값을 담는 연산자라고 했습니다. 따라서 &ptr&num의 주소는 서로 다른 것입니다.

 

또한, 배열처럼 인덱스로 접근할 수 있습니다.

pb[0] = 3;
printf("%d %d", a, pb[0]); // 3 3 출력

이것이 가능한 이유는, 포인터 변수가 접근할 주소와, 접근할 크기를 가지고 있기 때문입니다. 접근할 주소는 포인터 변수의 값이 되는 것이고, 접근할 크기는 사용하고자 하는 자료형의 크기가 됩니다.

int형 포인터라면 접근할 크기는 int형의 크기인 4바이트가 되겠죠?

 

다시한번 &기호를 보자면 결국 &기호도 포인터입니다.

&기호는 그 변수의 포인터형을 반환해줍니다. 즉, 위 코드에서 &aint형 포인터이고, 크기는 8바이트이며, 변수 a의 주소를 나타내고 있습니다.


참고로 포인터에 특정 자료형을 지정하지 않고 메모리 주소만 저장하는 방법도 있습니다. 그 것이 바로 void형 포인터입니다.

 

이 다음 글에선 포인터끼리의 형변환과, 배열의 포인터, 동적할당에 대해 다뤄보도록 하겠습니다.

이상 포인터(Pointer) .. 왜 쓸까요? (1) 였습니다. ^_^

반응형
LIST

'C > 개념' 카테고리의 다른 글

Hash 구현  (0) 2019.08.18
Hash 알고리즘  (0) 2019.08.10
C언어의 메모리 동적 할당  (0) 2019.06.23
C언어의 메모리 구조  (2) 2019.06.23
포인터(Pointer) .. 왜 쓸까요? (2)  (0) 2019.06.20