[C언어/C++] 포인터 사용법 & 예제 총정리

    포인터란?

    포인터는 주소를 저장하는 변수입니다. 기존의 변수는 데이터를 저장하는데 반하여 포인터는 메모리 공간의 주소를 저장합니다. 포인터 변수가 주소를 저장하려면 변수의 주소를 알아야 하는데 변수 이름 앞에 & 연산자를 붙이면 해당 변수의 시작 주소를 반환합니다. 더 나아가서 포인터 변수가 저장하는 변수의 주소에 저장된 값을 참조하려면 * 연산자를 사용합니다.

     

    포인터 변수의 선언 및 사용

    포인터 변수도 변수이므로 사용하려면 먼저 선언을 해야합니다. 다음은 포인터 변수의 선언 형식을 나타내고 있습니다. 포인터 변수의 선언은 자료형 다음에 *연산자를 붙이고 포인터 변수의 이름을 쓰게 됩니다.

    #include <stdio.h>
    
    int main() {
        int* p = NULL;
        int i = 10;
        p = &i;
        
        printf("변수 i의 주소값 : %p \n", &i);
        printf("포인터 p의 값 : %p \n", p);
        printf("포인터 p이 가리키는 값 : %d \n", *p);
        
        return 0;
    }
    

    C언어에서의 포인터 변수의 선언과 사용은 위와 같이 진행됩니다. 먼저 포인터 변수를 선언할 때는 담고자 하는 자료형에 *(참조 연산자)를 붙여서 선언합니다. 만약 int형 변수의 주소를 담고 싶으면 int *변수 이름 이렇게 말이죠. 여기서 변수의 크기가 자료형에 따라 달라지는 것처럼 포인터 변수도 자료형에 따라 크기가 달라지기에 여러 가지 자료형의 포인터 변수가 있는 것으로 착각을 하실 수도 있는데 그렇지 않고 모든 포인터 변수의 크기는 같습니다. 값을 저장하는 것이 아닌 주소 값을 저장하기 때문입니다. 그렇기에 32비트 운영체제라면 4바이트, 64비트의 운영체제라면 8바이트로 모든 자료형의 포인터 변수의 크기가 같습니다. 다만 포인터 연산을 수행할 때 그 주소에 있는 값을 읽어야 하는 부분이 생길 수 있어 어떠한 자료형의 주소인지를 알려주기 위해 여러 가지 변수의 타입으로 포인터 변수를 선언하는 것입니다. 

    위의 소스코드 내용을 그림으로 표현하자면 위와 같습니다. i라는 변수를 선언하고 메모리의 특정 주소에 할당이 되면 그 변수의 주소를 가르키는 포인터 변수 p를 선언합니다. i 변수의 주소를 p 포인터 변수에 대입할 때는 &연산자를 사용하며 그 주소에 있는 변수의 값을 가지고 올 때는 *연산자를 사용합니다.

     

    포인터 연산

    포인터 또한 변수이므로 값을 더하거나 뺄 수 있습니다. 다만 곱셈이나 나눗셈은 불가능합니다. 포인터 변수에 대한 연산은 일반적인 변수에 대한 연산과는 다릅니다. 포인터에 증가 연산 ++을 적용하였을 경우, 증가되는 값은 포인터와 변수의 자료형 바이트 크기만큼 증가된다. 즉 char 형일 때는 1씩, short형이면 2씩, int나 float 형이면 4씩, double 형이면 8씩 증가합니다. --연산자를 이용한 감소 연산도 마찬가지입니다. 포인터에 정수를 더하거나 뺄 때도 마찬가지입니다. 포인터에 정수를 더하면 포인터의 값이 단순히 그만큼 증가하는 것이 아니라 마찬가지로 포인터가 가리키는 자료형의 크기만큼 증가하게 됩니다. 즉 포인터의 자료형의 크기가 n일 때 포인터에 정수 m을 더하면 포인터 값은 n*m만큼 증가합니다. 증감 연산자를 포인터에 적용할 수도 있고 포인터에 가리키는 대상에 적용할 수도 있습니다. 수식은 아래의 표를 참고해주시기 바랍니다.

     

    포인터 연산 시 주의할 점은 연산자 우선순위로 인해 결과값이 달라질 수 있다는 부분입니다. 이 부분에 대해서는 이전에 포스팅한 내용이 있으므로 참고해주시면 감사하겠습니다.

    [C언어] 연산자 우선순위에 대하여

     

    포인터와 배열의 관계

    #include <stdio.h>
    int main() {
        int a[] = {10,20,30,40,50};
        printf("배열 a[0]의 주소값 = %p \n", a);
        printf("배열 a[1]의 주소값 = %p \n", a + 1);
        printf("배열 a[0]의 값 = %d \n", *a);
        printf("배열 a[1]의 값 = %d \n", *(a + 1));
        return 0;
    }
    

    포인터 배열은 서로 밀접한 관계를 가지고 있습니다. 배열 이름을 포인터라 생각하고 *a를 출력하여보면 첫 번째 요소 a[0]인 출력 됩니다. 또한 a+i는 a가 포인터이므로 배열 시작 주소에 (i*배열요소의 크기)이 더해집니다. 따라서 a+i는 &a[i]와 같고 또한 *(a+i)는 a[i]와 완전히 동일합니다. 

     

    위의 소스코드 내용을 그림으로 표현하자면 위와 같습니다. 

     

    간단한 포인터 사용의 예

    #include <stdio.h>
    
    void swap(int *num1, int *num2) {
        int temp = *num1;
        *num1 = *num2;
        *num2 = temp;
    }
    
    int main() {
        int num1 = 10;
        int num2 = 20;
    
        printf("num1의 값 : %d\n", num1);
        printf("num2의 값 : %d\n", num2);
    
        swap(&num1, &num2);
        printf("\nSwap함수 실행 후\n\n");
        
        printf("num1의 값 : %d\n", num1);
        printf("num2의 값 : %d\n", num2);
    }
    

    포인터의 장점은 어느곳에 있든지 주소를 직접적으로 참조함으로써 값을 가져올 수 있고 변경할 수 있다는 것이 장점입니다. 그렇기에 위의 예제처럼 main함수에서 선언하였기에 main에서만 접근할 수 있는 num1과 num2의 주소 값을 swap함수로 넘기고 이를 포인터 변수로 받아 사용할 수 있습니다. 이를 다른 말로 call by reference라고 합니다. 만약 주소 값을 넘겨주지 않고 그냥 변수의 값을 매개변수로 전달하였을 때는 값이 복사되어 함수로 전달됩니다. 이를 다른 말로 call by value라고 하며 이렇게 되었을 시 원본의 num1와 num2의 자리를 바꾼 것이 아니기에 전혀 의미가 없는 함수의 동작이 되었겠지만 주소 값을 넘겨줌으로써 직접적으로 num1와 num2의 자리를 바꿀 수가 있게 됩니다. 

     

     포인터 사용 시 주의점 

    포인터 변수 선언시 초기화 하기

    만약 포인터가 선언만 되고 초기화되지 않았다면 포인터는 쓰레기값을 가지게 됩니다. 이런 상태에서 포인터를 사용하여 메모리의 내용을 변경한다면 문제가 생길 수 있을 소지가 있습니다. 만약 우연히 포인터가 중요한 값을 가리키고 있었다면 중요한 정보를 덮어쓸 수도 있으며 따라서 전체 시스템을 다운시킬 수도 있으니 그냥 포인터를 단순히 선언만 하지 말고 선언 시 초기화를 사용하거나 NULL 포인터로 만들어주는 것이 바람직합니다.

     

    NULL 포인터의 사용

    포인터가 아무것도 가리키지 않을 때는 NULL로 설정하는 것이 좋습니다. NULL은 헤더 파일 stdio.h에 0으로 정의되어 습니다. 주소 0은 CPU가 사용하는 영역이어서 일반 프로그램은 주소 0에 접근할 수 없기에 안정성이 높습니다.

     

    포인터 자료형과 변수의 자료형 일치

    만약 포인터의 자료형이 변수의 자료형보다 크다면, 포인터를 통해 변수의 주소를 통해 쓰게 될 경우 변수의 범위를 넘어가서 이웃 바이트를 덮어쓰게 되어 문제가 생길 수 있습니다.

     

    절대 주소 사용 금지

    절대 주소는 아두이노와 같은 엠베디드 시스템에서만 사용합니다. 그 이유는 PC는 윈도우와 같은 운영체제가 프로그램을 관리하기 때문에 프로그래머가 사용하고자 하는 주소가 어떠한 용도로 사용되는지 모르기 때문입니다. 실행을 할 때마다 할당되는 주소가 다르기에 절대 주소는 사용하시면 안 됩니다.

     

     

     

    댓글(1)

    Designed by JB FACTORY