C언어 배열과 포인터 포인터

    2018-02-06 17:05:13 작성

    포인터는 메모리의 주소값을 저장하기 위한 특별한 변수 입니다.

    메모리의 주소

    변수를 선언하면 메모리 공간을 할당하고 할당된 공간에 값을 저장하여 사용할 수 있습니다.
    메모리는 고유한 주소값을 갖게 됩니다.
    변수에 &(주소연산자)를 사용하면 해당 변수의 주소를 읽을수 있습니다.

    #include <stdio.h>
    
    int main(void) {
        char ch1 = 'A', ch2 = 'B';
        int num = 10;
        printf("%p\n", &ch1);
        printf("%p\n", &ch2);
        printf("%p\n", &num);
        return 0;
    }


    위의 코드를 실행하면 변수의 주소를 볼수 있습니다.(실행할때 마다 매번 주소가 바뀝니다.)
    변수가 할당된 형태를 메모리상에서 표현된것을 상상해 본다면 다음과 같을 수 있습니다.

    문자 'A'가 저장되어 있는 변수 ch1의 주소값은 0x33FB77 에 할당되고, char형 임으로 1byte크기를 갖고
    문자 'B'가 저장되어 있는 변수 ch2의 주소값은 0x33FB78 에 할당되고, char형 임으로 1byte크기를 갖고
    그렇다면 int형 변수 num은 어디에 할당되 있나요?
    "int형 변수 num은 0x33FB79 에서 부터 0x33FB78C 까지 할당되어 있습니다."
    하지만 C언어에서는 시작 번지만을 가지고 위치를 표현합니다.
    int형 자료는 4byte 임으로 시작위치를 알면 끝이 어딘지는 쉽게 계산이 가능하기 때문입니다.
    "int형 변수 num은 0x33FB79​ 할당 되어있습니다." 라고 말할수 있습니다.
    포인터 변수는 메모리의 주소값을 저장하기 위한 변수 이므로 0x33FB79와 같은 메모리의 주소값을 저장할 수 있습니다.


    포인터 변수 선언

    포인터는 *(간접지정연산자)를 이용해 표현하며 다음과 같이 선언합니다.

    자료형 * 변수이름;

    포인터변수의 자료형은 포인터가 가리키는 값의 크기를 나타냅니다.

    #include <stdio.h>
    
    int main(void) {
        int num = 10;
        int * ptr; // int형 변수의 주소값을 저장하는 포인터 변수 선언
        ptr = &num; // num의 주소를 포인터에 저장
        printf("num의 주소값 : %p\n", &num);
        printf("포인터 ptr값 : %p\n", ptr);
        printf("ptr이 가리키는 값 : %d\n", *ptr);
        num = 20; // num의 값을 변경;
        printf("ptr이 가리키는 값 : %d\n", *ptr);
        *ptr = 30; // 포인터가 가리키는 값을 변경
        printf("num 값 : %d\n", num);
        return 0;
    }


    포인터 변수로 값을 참조하려면 *(간접지정연산자)를 사용하여 값을 참조할 수 있습니다.


    포인터의 자료타입과 크기

    #include <stdio.h>
    
    int main(void) {
        int num = 10;
        char c = 'A';
        double pi = 3.14;
    
        int * pnum = &num; // int형 포인터
        char * pc = &c; // char형 포인터
        double * ppi = &pi; // double형 포인터
    
        printf("int형 포인터의 크기 : %d\n", sizeof(pnum));
        printf("char형 포인터의 크기 : %d\n", sizeof(pc));
        printf("double형 포인터의 크기 : %d\n", sizeof(ppi));
    	return 0;
    }


    포인터는 모두 4바이트의 크기를 갖고 있는걸 알 수 있습니다.
    우리가 처음 프로젝트를 생성할때 32비트 콘솔 응용프로그램을 사용해서 만들었기 때문에 32bit 즉 4바이트의 주소체계를 갖는 응용프로그램이기 때문입니다.
    과거 16bit 컴퓨터에서는 2바이트 주소체계를 사용했습니다.
    요즘은 64bit의 컴퓨터를 사용하지만 우리가 현제 작업하는것은 32bit 환경이기 때문에 4바이트를 갖고 실제 64bit 시스템에서 동작하는 컴파일러로 컴파일시 8바이트의 크기를 갖는 포인터를 사용할 수 있습니다.


    잘못된 포인터의 사용

    그렇다면 포인터의 자료형은 왜 구분을 할까요?
    포인터의 자료형은 메모리 공간을 참조하는 크기의 기준이 됩니다.
    int형 포인터 자료형은 4byte를 참조하고, double형 포인터는 8byte를 참조하게 됩니다.

    #include <stdio.h>
    
    int main(void) {
        double a = 3.14;
        int * p = &a; // 자료형이 불일치
        printf("%d\n", *p); //예측불가능한 의미없는 출력
        return 0;
    }


    8byte인 double형 변수 a를 int형 포인터 변수 p로 4byte만을 참조하기 때문에 6행의 출력은 알수 없는 의미없는 값을 출력합니다.


    null 포인터

    포인터를 선언만 하는 경우 사용할 수 없는 쓰레기 값으로 초기화 됩니다.

    int * ptr; // ptr은 쓰레기값으로 초기화 됨

    잘못된 쓰레기 값을 가지고 있는 포인터로 연산을 할 경우 프로그램이 멈추는 현상도 일어날 수 있고, 잘못된 메모리에 접근에 대한 보호장치가 없는 운영체제의 경우 치명적인 영향을 줄 수도 있기 때문에 포인터는 항상 나중에 값을 넣더라도 초기화 하는게 좋습니다.
    이때 포인터에 0값을 대입하여 초기화하고 이는 메모리주소의 0번지를 가리키는게 아닌 아무곳도 가리키지 않는다는 의미로 해석되며 이런 포인터를 널포이터 라고 합니다
    다음과 같이 초기화 하세요.

    int * ptr = 0; // ptr은 쓰레기값으로 초기화 됨
    int * ptr = NULL // NULL은 stdio.h 에서 정의된 매크로 상수

    연습문제
    다음 코드를 보고 결과값을 예측해보세요.
    #include <stdio.h>
    
        int main(void) {
            int num = 10;
            int *ptr1 = &num;
            int *ptr2 = ptr1;
    
            (*ptr1)++;
            (*ptr2)++;
            printf("%d\n", num);
            return 0;
        }

    1. int형 변수 a와 b를 선언하고 각각 10과 20으로 초기화 합니다
    2. int형 포인터 변수 aptr, bptr 을 선언해 각각 a, b를 가리키게 합니다.
    3. aptr을 이용해서 num1의 값을 1 증가
    4. bptr을 이용해서 num2의 값을 2 감소
    5. a, b, 그리고 aptr과 bptr이 가리키는 값을 출력해보세요.
    6. 두개의 포인터 변수가 가리키는 대상을 바꾸봅니다. 즉 aptr은 num2를 가리키게 하고 bptr은 num1을 가리키게 합니다.
    7. 변경후 a, b 그리고 aptr과 bptr이 가리키는 값을 출력해보세요.