Programming/C

C언어 - 포인터의 이해

JunsuKim 2021. 9. 27.
728x90

포인터

포인터는 주소를 가리킨다. 이름만 포인터이지 int, char와 같이 변수이다. 이를 포인터 변수라 부른다.

변수가 어떤 값을 저장하는 것처럼, 포인터는 변수의 주소 값을 저장한다.

 

 "정수 6이 저장된 int형 변수 num을 선언하고 이 변수의 주소 값 저장을 위한 포인터 변수 pnum을 선언한 후, pnum에 변수 num의 값을 저장하자."와 같은 문장이 요구하는 바를 코드로 작성해보자.

#include<stdio.h>

int main() {
    int num = 6;
    int *pnum;
    pnum = &num;
    
    printf("int형 변수 num의 주소: %d\n", &num);
    printf("포인터 pnum의 값: %d\n", pnum);
    printf("포인터 punm이 가리키는 값: %d\n", *pnum);
    return 0;
}

int *pnum은 포인터 변수의 선언 방법이다.

pnum - 포인터 변수의 이름

int * - int형 변수의 주소 값을 저장하는 포인터 변수의 선언

이어서 코드를 보면 pnum = &num이 있다.

&연산자는 전 글에서 잠깐 설명을 했었듯이, 오른쪽에 등장하는 피연산자의 주소 값을 반환하는 연산자이다.

따라서 &연산의 결과로 변수 num의 주소 값이 반환되어 pnum에 저장하게 된다.

포인터의 선언 방법

위에서 이미 포인터 선언 방법을 보았었다.

int형 포인터 변수를 선언하기 위해서는 int *변수명을 사용하였다.

따라서 타입 별 포인터 변수를 선언하는 방법은 "type *포인터명"과 같이  하면 된다.

int *         //int형 포인터
int *pnum     //int형 포인터 변수 pnum

double *      //double형 포인터
double *pnum //double형 포인터 변수 pnum

type *       // type형 포인터
type * pnum  //type형 포인터 변수 pnum

&연산자

&연산자는 전 글에서도 간단하게 설명했던 적이 있다. 이를 좀 더 자세히 알아보자.

&연산자는 피연산자의 주소 값을 반환하는 연산자이다.

int main() {
    int num = 5;
    int *pnum = &num;
    . . . .
}

따라서 다음의 형태로 연산문을 구성하게 된다.

 

&연산자의 피연산자는 변수여야 한다. 상수는 될 수 없다. 또한 변수의 자료형에 맞지 않은 포인터 변수를 선언하면 연산 과정에서 문제가 발생할 수 있다.

#include<stdio.h>

int main() {
    int n = 5;
    double *pnum = &n;
    printf("%lf", *pnum);
    return 0;
}

*연산자

*연산자는 포인터가 가리키는 메모리 공간에 접근할 때 사용한다.

#include<stdio.h>

int main() {
    int n = 5;
    int* pnum = &n;
    *pnum = 20;
    printf("%d\n", *pnum);
    return 0;
}

// 20

코드를 보면

"포인터 변수 pnum이 변수 num을 가리키게 하고, 후에 포인터 변수 pnum이 가리키는 메모리 공간인 변수 num의 값을 20으로 저장해라"이다.

결국은 *pnum은 포인터 변수 pnum이 가리키는 변수 num을 의미하는 것이다.

 

다른 예제를 보면

#include<stdio.h>

int main() {
    int n = 10;
    int *p1 = &n;
    int* p2 = p1;
    (*p1)++;
    (*p2)++;
    printf("%d", n);
    return 0;
}

// 12

포인터 변수 p1은 n을 가리키고 포인터 변수 p2는 p1을 가리킨다. 결론적으로 포인터 변수 p2는 n을 가리키는 것이다.

*p1과 *p2를 한 번씩 ++해주면 n이 2번 증가하는 것과 같으므로 12가 출력된다.

잘못된 포인터의 사용과 널 포인터

포인터 변수에는 메모리 주소 값이 저장되고, 이를 이용해 메모리 공간에 접근도 하기 때문에 많은 주의를 필요로 한다.

잘못된 사례를 알아보면,

첫 번째로는 포인터 변수를 선언만 하고, 초기화를 하지 않는 것이다.

int main() {
    int *p;
    *p = 50;
    . . . .
}

포인터 변수를 초기화하지 않으면 쓰레기 값으로 초기화되어 어디를 가리킬지 모르게 된다. 여기서 값을 저장하는 것은 위치가 어디인 줄도 모르게 값이 저장되는 것이다. 메모리 공간이 중요한 위치였다면 시스템 전체에 심각한 문제를 끼칠 수도 있기 때문이다. 요즘은 운영체제에서 잘못된 메모리 접근의 시도가 있다면 이를 감지해 프로그램을 중지시킨다.

 

두 번째는 포인터 변수에 정수를 저장하는 것이다.

int main() {
    int *p = 125;
    *p = 20;
    . . . .
}

포인터 변수 p를 초기화한다 하고 125를 저장하였다. 여기서 25번지가 어떤 위치인 줄 알고 포인터 변수를 초기화한 것일까? 결론적으로는 첫 번째 예시와 다를 게 없는 것이다.

포인터 변수를 선언만 해놓고, 이후에 유효한 주소 값을 채워 넣고 싶다면 다음과 같이 하는 것이 좋다.

int main() {
    int *p = 0;
    int *p2 = NULL;
    . . . .
}

이와 같이 0으로 초기화를 하거나 NULL을 사용하면 된다. 0을 가리켜 "널 포인터"라 하며 0 = NULL인 것이다.

이는 0번지를 가리키는 것이 아닌, 아무 곳도 가리키지 않는 것을 의미한다.

널 포인터를 사용한다 해서 프로그램이 중지되지 않는 것은 아니지만, 문제가 생기는 것을 방지할 수 있다.

따라서 포인터 변수를 선언만 해놓고 싶을 때는 널 포인터를 이용하여 초기화해두는 것이 좋다.

728x90

'Programming > C' 카테고리의 다른 글

C언어 - 포인터와 함수에 대한 이해  (4) 2021.09.30
C언어 - 포인터와 배열  (0) 2021.09.29
C언어 - 1차원 배열  (0) 2021.09.23
C언어 - 지역변수와 전역변수  (0) 2021.09.22
C언어 - 함수(Function)  (0) 2021.09.22

댓글