const 키워드

 

포인터를 사용하는 프로그래밍에 발생할 수 있는 오류는 주소값과의 혼동이다.

 

*ptr 는 값을 가리키는 것이고,

 

ptr 은 주소를 가리키는 것이다. 주소를 가리키는 이것이 실직적으로 저장된 값이다.

 

이전 포스트에서 작성한 값을 바꾸는 스왑함수는 아래와 같이 작동한다. 어떤 숫자를 넣건 두개의 숫자를 바꾼다.

void Swap(int * ptr1, int * ptr2){
    int temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}

 

다시 아래의 함수를 보자. * 연산자가 빠지면서 주소에 주소를 대입하였다. a에 7 b 에 3을 넣어본다.

void Swap(int * ptr1, int * ptr2){
    int temp = *ptr1;
    ptr1 = ptr2;
    *ptr2 = temp;
}

a는 temp 에 저장되고 ptr1에 ptr2 의 주소가 복사된다.

 

아래와 같은 계산이 진행되도 C컴파일러는 오류를 감지할 수 없다.

 

다음의 코드는 const 키워드로 이 문제를 방지할 수 있도록 해준다.

#include <stdio.h>

void Swap(int * const ptr1, int * ptr2){
    int temp = *ptr1;
    ptr1 = ptr2;
    *ptr2 = temp;
}

void main(){

    int a = 7;
    int b = 3;

    printf("a: %d b: %d \n", a, b);
    Swap(&a,&b);
    printf("a: %d b: %d \n", a, b);

}

const 는 read-only 매개변수이다. 따라서 pointer 값은 변경 불가능하다. const 키워드는 변수를 상수로 바꿔준다.

 

한편 const 는 두 개의 위치에 사용할 수 있는데

 const int *ptr
 
 int * const ptr

const int 는 *ptr의 값을, int * const ptr 은 ptr 값의 변경을 막는다. *ptr값은 변수에서 정해지기 때문에 문제가 초기화의 필요가 없을테고 ptr의 경우 const 명령어는 한번만 값의 할당이 가능하다.

 

두개 다 사용하는 방법은 아래와 같다.

const int *const ptr

ptr 값에만 한번의 값의 대입이 가능하다.

 

 

포인터 주소의 연산

포인터 주소의 연산은 타입에 의하여 결정된다. 즉 32비트 int 형의 경우 하나의 저장소가 4바이트의 단위로 연산할 수 있다. 포인터 연산 방식은 일반 변수와 비슷하나 전혀 다르므로 주의를 요한다.

 

int *ptr1 은 32비트 정수형 포인터이다.

 

ptr1에는 메모리 주소값이 저장되어 있다.

 

주소연산은 ptr1 = ptr1 + 1 단위로 한다. +1은 4바이트 이동을 의미한다. 그러니까 주소값 0x0000에서 +1은 0x0004가 된다. 이게 포인터 연산의 트릭이므로 헷갈리면 안된다.

 

좀더 알아보기 위해 다음 소스코드로 주소연산을 한다.

#include <stdio.h>

void main(){

    int n1 = 7;
    int *ptr1 = &n1;
    int n2 = 36;
    int *ptr2 = &n2;
    int n3 = 205;
    int *ptr3 = &n3;

    printf("%5d : %p\n",n1,ptr1);
    printf("%5d : %p\n",n2,ptr2);
    printf("%5d : %p\n",n3,ptr3);

    printf("\n===================\n");

    ptr1 = ptr1 + 1;
    printf("%p : %p\n",*ptr1,ptr1);
    ptr1 = ptr1 + 1;
    printf("%p : %p\n",*ptr1,ptr1);
    ptr1 = ptr1 + 1;
    printf("%p : %p\n",*ptr1,ptr1);

}

 

결과값

앞부분에서는 포인터와 주소를 출력해본다. int 형 정수 3개, 포인터 3개를 선언했다는 부분을 본다. 이제 생각을 메모리로 확장시켜 본다. 정확하게 런타임시 C프로그램의 행동을 예측하기 어렵지만 데이터 작업을 통해서 유추는 가능하다. C설계자가 C언어를 어떤 구조로 만들었는지 생각해본다.

 

포인터 연산 이전의 메모리 주소를 보자. 36을 가리키는 0061FE00 이 있다.

(C컴파일러가 64비트 버전이라 메모리가 길다. 실제로 저 뒷부분의 용량까지는 학습에 사용할 일이 없다)

 

포인터 연산을 시작하면서 +3 까지(12바이트) 이동을 시켜봤다.

 

마지막 포인터 ptr1이 가리키는 곳의 값이 0061FE00 으로 36을 가리키는 ptr2의 값과 일치한다. 이는 ptr1 이 가리키는 값이 ptr2의 메모리 주소임을 알 수 있다. C컴파일러의 설계자가 스택에 변수와 메모리 주소를 근처에 저장하도록 설계했을 거라 생각할 수 있다. 단지 C언어를 배울 때 그 규칙은 알려주지 않기 때문에 모를 뿐이다.

 

이것이 그 동안 유능한 C언어 강사들이 포인터 강의를 하며 전달했던 내용이다. 주소를 기억하고 저장하고 연산하는 방식은 일반 변수와 다르게 취급하는게 당연하다. 단지 프로그램 내에서 생긴 모습이 일반 변수와 같기 때문에 혼동스러울 뿐이다. 전혀 다른 종류의 타입으로 생각하면 알 수 있는 부분이다.

 

포인터 체인(이중 포인터)

포인터 연산자를 중복사용하여 연결을 할 수 있다. 포인터 변수가 전의 포인터 주소를 가리키는 방식으로 물고 물려 들어간다. 결국은 몇 단계를 거치더라도 목표 값에 도달할 수 있다. 아래는 하나의 값에 도달하기 위햇 세번의 포인터를 거치는 예제이다.

#include <stdio.h>

void main(){

int num1 = 25;
int *ptr1 = &num1;
int **ptr2 = &ptr1;
int ***ptr3 = &ptr2;

printf("value : %d address1 : %p \n",num1, &num1);
printf("value : %d address1 : %p \n",*ptr1, ptr1 );
printf("value : %d address1 : %p address2 : %p \n",**ptr2, *ptr2, ptr2 );
printf("value : %d address1 : %p address2 : %p address3 :%p\n",***ptr3, **ptr3, *ptr3, ptr3 );

}

 

 

공유하기

facebook twitter kakaoTalk kakaostory naver band