Skip to Main Content

C언어 기초 가이드 STEP 2: 포인터

코딩 학습 가이드는 소프트웨어학과 송오영교수님이 검수하였습니다.

메모리 주소

지금까지 값을 저장할 때 변수를 사용했습니다. 변수는 컴퓨터의 메모리에 생성됩니다.
C언어에서 모든 변수는 메모리상 주소를 가지고 있습니다. 메모리는 데이터를 저장하는 공간으로, 그 위치를 식별할 수 있어야 합니다.
포인터를 학습하기에 앞서, 변수들이 어떤 식으로 메모리에 저장이 되는지에 대해 알아야 합니다.

변수의 메모리 주소를 구하기 위해서는 변수 앞에 &(주소 연산자)를 붙입니다. 주소는 해당 데이터가 저장된 메모리의 '시작 주소'를 의미합니다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main(void)
{
    int num = 10;
 
    printf("%p\n"&num);
 
    return 0;
}


메모리 주소는 00AFF9F4와 같이 16진수 형태이며, printf에서 서식 지정자 %p를 사용하여 출력합니다.
(8진수를 출력하는 %o, 16진수를 사용하는 %x도 사용가능합니다.)

메모리 주소는 고정된 것이 아니라 컴퓨터마다, 실행할 때마다 달라집니다.

 

실행결과

00AFF9F4
 

 

int형 변수는 4바이트이므로 int형 변수 num은
00AFF9F4번지에서부터 00AFF9F7번지까지 걸쳐있습니다.

변수 num이 저장되기 시작한 주소 00AFF9F4가 변수 num의 주소값입니다.

주소값 00AFF9F4 또한 16진수로 정수입니다.
따라서 주소값도 저장이 가능하고, 이 주소값을 저장하기 위한 변수가 바로 포인터 변수입니다.

포인터의 기본 개념

포인터(pointer)란 메모리의 주소값을 저장하는 변수입니다.
포인터 변수는 * (참조연산자)를 사용하여 선언합니다

C언어에서 포인터는 다음 문법에 따라 선언합니다. 자료형은 포인터가 가리키고자 하는 변수의 자료형을 명시합니다.

형식

자료형 * 포인터이름;


초기화하지 않은 채로 참조 연산자를 사용하게 되면 의도하지 않은 메모리 장소에 값을 저장하는 것이 됩니다.
이러한 동작은 오류를 발생시킵니다. 따라서 다음과 같이 포인터의 선언과 동시에 초기화를 함께 하는 것이 좋습니다.

형식

자료형 * 포인터이름 = &변수이름;
자료형 * 포인터이름 = 주소값;

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main(void) {
 
    int num;    // 일반 변수 선언
    int * pnum;    // 포인터 선언
    
    pnum = &num;    // 포인터에 num 주소 대입
    *pnum = 10;    // 포인터로 변수 num에 10 
 
    printf("변수명으로 num 값 출력 : %d\n", num);
    printf("포인터로 num 값 출력 : %d\n"*pnum);
 
    return 0;
}

실행결과

변수명으로 num 값 출력 : 10
포인터로 num 값 출력 : 10
 

 

위 예제의 흐름을 그림으로 정리하면 다음과 같습니다.

5행, 6행에서 일반 변수와 포인터를 선언하는 부분입니다.
6행에서 *는 포인터를 표시하는 기호입니다. 5행에 선언된 변수의 형태가 int형이므로 int를 사용하여 포인터를 선언합니다.

 

8행에서는 포인터에 num의 시작 주소를 저장합니다. 이제 포인터 pnum은 변수 num의 위치를 기억하고 있습니다.

 

이렇게 포인터가 어떤 변수의 주소를 저장한 경우 '가르킨다'라고 하고, 'pnum이 num을 가르킨다' 라고 말할 수 있습니다.

 

포인터 pnum이 num을 가리키므로 *pnum에 10을 대입하면 결국 num에 10을 대입하는 것과 같습니다.

이중 포인터

이중 포인터(포인터의 포인터)는 포인터 변수를 가리키는 또 다른 포인터 변수를 뜻합니다.
포인터를 선언할 때 *를 두 번 사용하면 이중 포인터를 선언합니다.

형식

자료형 ** 포인터이름;

포인터를 선언할 때 *의 개수에 따라서 삼중 포인터, 사중 포인터 그 이상도 만들 수 있습니다.


예제를 통해 이중 포인터의 선언과 사용법을 살펴보겠습니다.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main(void)
{
    int * ptr;     // 포인터 선언
    int ** dptr;    // 이중 포인터 선언
    int num = 10;
 
    ptr = &num;    // 포인터에 num 주소 대입 
 
    dptr = &ptr;    // 이중 포인터에 ptr 주소 대입
 
    printf("%d\n"**dptr);    // 포인터를 두 번 역참조하여 num의 메모리 주소에 접근
 
    return 0;
}
 
 

실행결과

10
 

9행에서 변수 num의 주소를 ptr에 저장하고, 11행에서 변수 ptr의 주소를 dptr에 저장했으므로 다음과 같이 그림을 그릴 수 있습니다.


dptr은 ptr의 주소를 가리키고 있고, ptr은 num의 주소를 가리키고 있습니다.
그렇기에 *dprt은 포인터 변수 ptr을, **dptr은 변수 num을 의미하게 됩니다.

포인터와 const

포인터 변수를 대상으로도 const 선언을 할 수 있습니다.
변수에 사용하는 const와는 다른 의미를 가집니다. 포인터에 사용하는 const는 상수로 만드는 선언이 아닌, 포인터를 통한 변경을 허용하지 않는다는 선언입니다.
const는 변수를 상수화시키는 역할입니다. 포인터가 상수화 되는 것이지 변수가 상수화 되는 것이 아닙니다.
즉, 변수 num은 언제든지 변경이 가능하나, 포인터를 통해 변수 num 값을 변경하는 것만 막을 수 있습니다.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main(void)
{
    int num = 10;
    const int* p = &num;
 
    *= 20;    // 컴파일 에러
 
    printf("변수 num : %d\n"*p);
 
    return 0;
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main(void)
{
    int num = 10;
    const int* p = &num;
 
    num = 20;    // 컴파일 성공
 
    printf("변수 num : %d\n"*p);
 
    return 0;
}
  8행 *p = 20 에서 p를 통해 num값을 바꾸고자 한다면
에러가 발생하게 됩니다.
 

실행결과

20