본문 바로가기
Development/C

(C언어 기초) 14. 포인터

by eoieiie 2023. 11. 5.

 변수의 저장공간


지금까지 변수의 선언으로 메모리에 공간을 확보하고, 그곳을 데이터를 넣고 꺼내 쓰는 공간으로 사용했습니다. 변수명은 그런 메모리 공간을 식별할 수 있는 이름이라고 할 수 있습니다. 

그러나 우리는 그 변수를 선언된 블록 안에서, 함수의 범위 안에서만 사용할 수 있었습니다. 같은 변수명을 사용한다고 하더라고 블록이나 함수가 다르면 언제나 다음과 같이  별도의 저장 공간을 확보하는 것이 필요했었습니다. 

 

int main(void)
{
    int a;
}



int new_func(void)
{
    int a;
}

//두 a는 각각 다른 변수로 지정된다.

 

포인터는 바로 이런 불편함을 해결하기 위해 사용됩니다. 사용 범위를 벗어난 경우에도 데이터를 공유할 수 있게 도와주는 포인터에 대해서 알아보도록 하겠습니다. 

 

메모리의 주소


메모리는 우리가 데이터를 넣고 꺼내 쓰는 공간이며, 그 특정한 위치를 식별할 수 있어야 합니다. 그 특정한 위치를  메모리의 주소라고 합니다. 주소의 크기는 바이트 단위로 구분되며, 이 값은 0부터 시작하고 바이트 단위로 1씩 증가하게 됩니다. 예를 들어,

 

int a;

 

라는 변수 a를 선언하는 순, 변수는 다음과 같이 4바이트에 걸쳐서(int의 크기는 4바이트)메모리에 할당됩니다. 

 

주소값 1 2 3 . . . . 100 101 102 103 104 . . . . .
메모리               변수 a가 할당된 영역            

 

따라서  a = 10; 과 같은 문장은 메모리의 100번지부터 103번지까지 총 4바이트의 공간에 10을 저장하며, a + 20 과 같은 수식은 메모리 100번지부터 103번지까지 4바이트에 저장된 값과 20을 더하는 연산을 수행합니다. 이렇게 우리는 변수명으로 메모리의 공간이나 값을 간단히 사용해왔습니다. 

 

주소 연산자 


 

이제부터는 변수를 이름이 아닌 주소로 사용하는 방법에 대해서 알아보도록 하겠습니다. 주소는 변수가 할당된 메모리 공간의 시작 주소를 의미하며, 시작 주소를 알면 그 위치부터 변수의 크기만큼 메모리를 사용할 수 있습니다. 예제를 통해 주소 연산자 &의 사용법에 대해서 알아보도록 하겠습니다. 

 

#include <stdio.h>

int main(void)
{
    int a;
    double b;
    char c;

    printf("int 형 변수의 주소 : %u\n", &a);
    printf("double 형 변수의 주소 : %u\n", &b);
    printf("char 형 변수의 주소 : %u\n", &c);

    return 0;
}

// int 형 변수의 주소 : 3359637004
// double 형 변수의 주소 : 3359636992
// char 형 변수의 주소 : 3359636991

 

int형 변수 a는 3359637004 번지부터 3359637008번지까지 총 4바이트, 

double형 변수 b는 3359636992 번지부터 3359637000번지까지 총 8바이트, 

char 형 변수 c는 3359636991 번지부터 3359636992번지까지 1바이트가 할당되었음을 확인할 수 있습니다. 

 

포인터와 간접 참조 연산자


 

이제부터는 변수에 할당된 메모리의 주소를 활용하는 방법에 대해서 알아보겠습니다. 포인터는 변수의 메모리 주소를 저장하는 변수입니다. 따라서 포인터 역시 변수처럼 선언 후 사용하는 것이 필요합니다. 선언시에는 변수 앞에 *만 붙여 주면 됩니다. 

 

#include <stdio.h>

int main(void)
{
    int a;
    int *pa;

    pa = &a; //포인터에 a의 주소 대입
    *pa = 10; // 포인터로 변수 a에 10 대입

    printf("포인터로 a값 출력 : %d\n", *pa);
    printf("변수명으로 a값 출력 : %d\n", a);

    return 0;

}

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

 

포인터 변수가 선언되면 일반 변수와 마찬가지로 메모리에 저장 공간이 할당되고, 그 이후에는 변수명으로 사용할 수 있습니다. pa = &a; 부분이 포인터에 a의 시작 주소를 저장하는 문장입니다. 만약 변수 가 메모리 100번지부터 103번지까지 할당되었다면 주소값인 100이 포인터에 저장됩니다. 

 

주소값 1 2 3 4 5 6 . . . 100 101 102 103 . . . . .
메모리     포인터 pa할당영역       변수 a의 할당영역          
      100       미할당          

 

이제 포인터pa는 변수 a가 메모리의 어디에 할당되었는지 그 위치를 기억하고 있습니다. 이렇게 포인터가 어떤 변수의 주소를 저장한 경우 'pa는 a를 가리킨다'  라고 표현합니다. 이는 pa는 포인터이며, 변수 a의 주소를 저장하고 있다는 뜻이 됩니다. 이렇게 포인터가 어떤 변수를 가리키게 되면 해당 변수를 사용할 수 있게 됩니다. 즉 포인터 pa로 변수 a를 사용할 수 있습니다. 포인터가 가리키는 변수를 사용할 때는 포인터에 특별한 연산자를 사용하는데, 이를 간접 참조 연산자(*)또는 포인터 연산자라고 합니다. 

 추가적으로 우리가 scanf 연산자를 사용할 때 주소값을 입력받았었는데, &a는 pa와 같은 표현이기 때문에 만약 입력을 받고 싶다면 다음과 같이 문장을 만들어도 됩니다. 

 

scanf("%d", &a);

// ==

scanf("%d", pa);

 

 

scanf("%d", pa) ㅡ> 100 ㅡ> 100 101 102 103
  pa의 값   변수 a의 주소(시작지점 100)

 

이제 사용법을 알았으니, 예제를 살펴보도록 하겠습니다. 다음은 포인터를 사용한 두 정수의 합과 평균의 계산입니다. 

 

#include <stdio.h>

int main(void)
{
    int a = 10, b = 15, total;
    double avg;
    int *pa, *pb;
    int *pt = &total;
    double *pg = &avg; // 가리키는 변수의 자료형에 맞추어 선언

    pa = &a; //선언 후에 포인터 변수에 메모리 주소를 할당할 때는 *를 사용하지 않는다.
    pb = &b;
    //*pa = &a; 라는 표현은 '포인터 pa가 가리키는 주소에 a의 주소를 저장하라'는 의미가 된다. 
    //이는 잘못된 표현이다. 왜냐하면 *pa는 포인터 pa가 가리키는 값, 즉 변수 a의 값을 의미하는데, 
    //여기에 메모리 주소를 저장하라는 명령은 올바르지 않다.

    *pt = *pa + *pb;
    *pg = *pt / 2.0;

    printf("두 정수의 값: %d, %d\n", *pa, *pb);
    printf("두 정수의 합: %d\n", *pt);
    printf("두 정수의 평균 : %.1lf\n", *pg);

    return 0;

}


// 두 정수의 값: 10, 15
// 두 정수의 합: 25
// 두 정수의 평균 : 12.5

 

 

const를 사용한 포인터


const 예약어를 포인터에 사용하면 변수의 값을 바꿀 수 없게 됩니다. 이는 변수에 사용하는 것과 다른 의미를 가집니다. 예제를 통해 확인해보겠습니다.

 

#include <stdio.h>

int main(void)
{
    int a = 10, b = 30;
    const int *pa = &a;

    printf("변수 a의 값: %d\n", *pa);
    pa = &b;
    printf("변수 b의 값: %d\n", *pa);
    pa = &a;
    a = 20;
    printf("변수 a의 값: %d\n", *pa);

    return 0;

}

// 변수 a의 값: 10
// 변수 b의 값: 30
// 변수 a의 값: 20

 

좀 이상하죠? 우리가 알던 const의 사용법대로라면 pa는 다른 변수의 주소를 저장할 수 없어야 하는데, 출력 결과에서 pa 는 const와는 상관없이 b 변수의 주소를 저장하고 그 값을 출력하고 있습니다. 

여기서 사용된 const는, pa 가 가리키는 변수 a는 pa를 b처럼 간접 참조하여 출력할 수 없다는 뜻입니다. 만약 *pa = 20; 과 같이 pa를 통해 a값을 바꾸려 한다면 에러를 출력합니다. 

 

댓글