본문 바로가기

언어/C

포인터의 이해(2) - 상수 포인터, 포인터의 덧셈과 뺄셈, 배열과 포인터의 관계, [] 연산자

상수포인터

 

const 키워드를 붙여 데이터를 상수(바뀌지 않은 값)로 만들 수 있다.

포인터 또한, const 키워드를 붙여 상수로 만들 수 있다. 

 

예를 들어보며 이해해보자

 

#include <stdio.h>

int main() {
    int a;
    int b;
    const int* p = &a;
    p = &b; //올바른 문장
    *p = 3; //올바르지 않은 문장
    return 0;
}

 

다음 예시는 컴파일을 했을 경우 에러가 난다.

 

 

const int* 의 의미는 const int 형 변수를 가리킨다는 의미가 아니고, int  형 변수를 가리키는데 그 값을 절대로 바꾸지 말라는 의미이다. 즉, p는 int형 변수를 가리키고 있고 const가 붙어있어서 p가 가리키는 변수의 값이 바뀌면 안되는 것이다.

 

여기서 변수 a는 값이 자유롭게 바뀔 수 있다. 그러나 p를 통해서 변수 a를 간접적으로 가리킬 때는 const로 인해서 값을 바꿀 수 없다.

그래서 *p = 3 ; 이라는 문장에서 오류가 발생한다.

 

그렇다면, p = &b;의 문장은 오류가 나지 않는다. 그 이유를 다음의 예와 함께 설명해보겠다.

 

#include <stdio.h>

int main() {
    int a;
    int b;
    int* const p = &a;
     p = &b; //올바르지 않은 문장
    *p = 3; //올바른 문장
    return 0;
}

 

다음의 예제도 컴파일을 하면 앞의 예와 같은 오류가 발생한다.

 int* p = &a ; 만 보았을 때 int 형을 가진 p의 포인터를 a의 주소값으로 정의했다. 이번에는 int* 와 p 사이에 const를 통하여 p의 값이 바뀌면 안되는 값으로 정의하였다.

즉, p에 a의 주소값이 저장되고 const를 통하여 p의 값이 바뀔 수 없으므로, p는 a를 가리키고 있다는 것이 바뀔 수 없다.

 

그래서 p = &b; 라는 문장이 오류가 발생하는 것이다.

 

 

 

포인터의 덧셈

 

다음으로 포인터의 덧셈과 뺄셈에 대해서 공부해보자.

 

먼저 int 형의 변수를 가리키고 있는 포인터에 1을 더해보자

 

#include <stdio.h>

int main() {
    int a;
    int *p = &a;
    
    printf("p의 값 : %p", p);
    printf("p + 1 의 값 : %p", p + 1);

    return 0;
}

 

 

결과를 확인해보면 두 수의 차이는 4인 것을 확인할 수 있다. (16진수인 것에 유의!)

 

왜 이러한 결과가 나오냐면 int  형이 4 바이트를 가지고 있기 때문입니다.

다른 예도 같이 들어보겠습니다.

 

#include <stdio.h>

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

    int *p = &a;
    double* pp = &b;
    char* ppp = &c;

    printf("p의 값 : %p \n", p);
    printf("p + 1 의 값 : %p \n", p + 1);

    printf("pp의 값 : %p \n", pp);
    printf("pp + 1 의 값 : %p \n", pp + 1);

    printf("ppp의 값 : %p \n", ppp);
    printf("ppp + 1 의 값 : %p \n", ppp + 1);

    return 0;
}

 

 

double 형의 주소값을 가진 b의 경우 +1을 한 경우 8이 차이나고,

char 형의 주소값을 가진 c의 경우 +1을 한 경우 1이 차이가 난 것을 확인할 수 있다.

뺄셈 역시 다음과 같은 원리로 결과가 나온다.

 

 

포인터와 배열

 

다음과 같이 배열을 정의해보자.

 

int arr[5] = {1, 2, 3, 4, 5};

 

그러면 arr라는 배열은 메모리 상에서 다음과 같이 나타난다.

int형 배열 하나의 원소는 4 바이트를 차지한다. 

 

 

예를 통하여 int 배열의 각 원소가 4 바이트를 차지하는지 주소값을 통하여 알아보자 

 

#include <stdio.h>

int main() {
    int arr[] = { 1,2,3,4,5 };

    for (int i = 0; i < 5; i++) {
        printf("arrr[%d] 의 주소값 : %p \n",i, &arr[i]);
    }
    
    return 0;
}

 

 

이처럼 4씩 증가하는 것을 확인할 수 있다. 그러면 배열의 시작 주소값을 담고있는 배열의 포인터를 정의한 다음에 +1을 더하면 그 다음 원소를 가리킨다는 것을 예측할 수 있다. 

왜냐하면 자신의 가리키는 데이터 타입의 크기를 곱한 만큼 덧셈이 수행되기 때문이다. 

다음의 예를 통하여 확인해보자. 

 

#include <stdio.h>

int main() {
    int arr[] = { 1,2,3,4,5 };
    int* p;
    
    p = &arr[0];

    for (int i = 0; i < 5; i++) {
        printf("arrr[%d] 의 값 : %d \n", i, *(p+i));
    }
    
    return 0;
}

 

 

 

배열의 주소값과 [] 연산자의 역할

 

배열을 출력하는 예를 들어보자.

 

#include <stdio.h>

int main() {
    int arr[] = { 1,2,3,4,5 };
    
    for (int i = 0; i < 5; i++) {
        printf("arrr[%d] 의 값 : %d \n", i, arr[i]);
    }
    printf("arr : %p", arr);
    return 0;
}

 

 

다음과 같이 []를 사용하면 배열 안의 값이 나오는 것을 확인할 수 있고, arr 만 확인하는 경우 해당 배열의 첫 번째 원소의 주소값이 출력되는 것을 확인할 수 있다.

 

이처럼, [] 연산자는 *(arr + i)로 바뀌어서 처리되는 것이다.