본문 바로가기
알고리즘

[C언어] 포인터 배열을 이해하고 있는지 확인 해보자.( ptr[0], ptr, &ptr[0] 이런게 애매하다면 확인하자.)

by itstime0809 2023. 10. 7.
728x90

C언어 포인터

 2학기 자료구조 및 알고리즘 강의를 수강하면서 오랫동안 사용 해보지 않았던 C언어를 이용해 학습을 하던 도중 포인터에 대한 개념이 정확히 잡히지 않는 것 같아 작성하게 되었다. ptr이 포인터 변수라고 가정하자. ptr[0], &ptr, ptr, ptr[0][0] 과연 얼마나 정확하게 알고 있을까. 그리고 매번 포인터를 배우면서 어딘가에서 자꾸 보게 되는 그 단어 혹은 문장 '배열의 첫 번째 주소값' 필자는 이게 너무 악마 같은 문장이라고 생각한다. 결코 쉽게 이해되는 문장이 아니라고 생각한다. (적어도 초심자에게는) 그래서 이러한 애매 모호한 표현들을 조금 더 쉽게 이해해 보고자 작성하게 되었다.

 

포인터

 구구절절 설명할 필요 없이 포인터는 결국 메모리 주소값을 가리키는 것은 이 게시글을 보고자 하는 분들이라면 알고 있는 개념이다. 바로 본론으로 들어가 보면 포인터가 메모리 주소값을 가리키고 있다는 것쯤은 누구나 쉽게 이해가 되지만 포인터 배열이라고 하면 쉽게 이해가 되지 않는 부분들이 있다. 여기서 쉽게 이해가 가지 않는다는 건 포인터의 주소값이 어디가 어디를 가리키고 있는지 쉽게 이해가 가지 않는다고 하자.

 

#include <stdio.h>

int main () {
    char *ptr[2];
    ptr[0] = "Korea";
    ptr[1] = "itstime";
    
    printf("%u\n", ptr);
    printf("%u\n", &ptr[0][0]);
    printf("%u\n", &ptr[0]);
    printf("%u\n", ptr + 0);
    printf("%u\n", *(ptr[0] + 1));
    printf("%c\n", *(ptr[0] + 1));
        

    return 0;
}

 

 우선  포인터 배열 단어를 잠깐 정의하고 시작해 보면, 포인터 배열이라고 하는 것은 포인터들을 가지고 있는 배열이다. 다른 말로 하면 메모리 주소값을 가지고 있는 배열이다. 자 ptr은 포인터 배열 변수다. 그렇다는 건 포인터 배열 변수가 가지고 있는 것은 배열이긴 하다. 맞나? 이게 맞다면 그 배열이 가지고 있는 요소들은 메모리 주소 값이 된다. (작성할 때 가끔 이렇게 물음표를 던지면 직접 답하면서 내려가보길 추천드립니다.)

 

그럼 이렇게 표현할 수 있을 것 같다. 우선 배열의 시작 주소값이 100이라고 가정하자. 배열의 요소값은 두 개가 존재하기 때문에 100과 101만 존재한다.

 

 

[100,101] 

 

 

 그렇다면 가정한다고 작성했던 문장에서 배열의 시작 주소값이 100이라는 의미가 도대체 무엇일까. 만약 100이라는 메모리의 주소값을 ptr[0]번지에 저장되어 있는 Korea의 K의 주소값이라고 생각했다면 아래와 같은 생각을 했을 것이다.

 

 

[문자열 K의 첫 번째 주소값, 문자열 i의 첫 번째 주소값]

 

 

 두 개는 서로 다르다. 그럼 다시 한번 체크해 보자. 배열의 이름은 배열의 시작주소값을 나타낸다고 한다. 현재 배열 안에 즉 [] 안에 들어 있는 문장은 배열의 시작 주소값이 맞는가? 그렇지 않다. 왜냐하면 배열의 시작주소값은 말 그대로 배열이 시작하는 주소의 값으로 사용되기 때문이다. 하지만 배열 안에 명시되어 있는 문장은 문자열 k의 첫 번째 주소값이라고 되어 있다. 따라서 둘은 서로 다른 주소 값이라는 것이다.

이를 조금 더 자세하게 이해하기 위해서 이렇게 생각해 볼 수 있다.

 

 

배열의 주소값 => 100,101

배열의 주소값이 가리키는 주소값 => [10,20]

 

 

 위에서 설명하고자 하는 것은 100번지는 배열의 시작 주소값 100을 나타내고 있고, 배열의 주소값들이 가리키는 값은 배열이 가지고 있는 메모리 주소값이다. 결국 100번지가 가리키는 것은 10이라고 하는 메모리 주소를 가리키게 되는 것이고, 101이라고 하는 주소값은 20이라는 메모리 주소를 가리키는 것이다. 그래서 만약 위 코드를 돌려보았다면, 첫 번째 ptr이 출력하는 메모리의 주소값과, &ptr[0][0]이 출력하는 주소값이 다르다는 것을 확인할 수 있다. 왜냐하면 ptr은 배열의 이름이기 때문에 배열의 주소값에서 시작하는 주소인 100을 가리키게 된다. 하지만 ptr[0][0]은 ptr[0]번째 인덱스를 참조한 뒤 [0] 다시 한번 0번째를 참조하기 때문에 Korea문자열 중 K의 값을 가리킨다.

따라서 100과 10을 가리키게 된다는 것이다.

 

 그럼 이건 어떨까 ptr과 &ptr[0]은 다를까 같을까. 결론은 같다. 왜 같은가? ptr은 배열의 시작주소값을 가지고 있다. 즉 100을 가지고 있다는 의미다. ptr[0]은 &가 없다면 10을 가리키게 될 것이다. 왜? 배열의 인덱스를 참조할 때 ptr[0]의 값을 출력하기 때문이다. 마찬가지로 ptr[0]에 &가 없다면 ptr[0]이 들고 있는 값을 출력하는 것이다. 단 포인터 배열이기 때문에 그 값이 메모리주소일 뿐이다. 그래서 &없으면 서로 다른 메모리 주소를 가리키는 게 맞지만 제시한 것은 &있다. 따라서 ptr [0]은 = 10이라는 메모리 주소를 가리키게 되겠지만, &ptr[0]라는 의미는, 결국 ptr[0]의 메모리 주소를 들고 있는 주소값을 의미하기 때문에. 100번이라고 할 수 있다.

 

 다음 코드를 보자 ptr + 0 이라고 되어 있는 부분을 보자면 ptr이 앞서 배열의 이름이자, 배열의 이름이 가리키는 것은 배열의 시작 주소 이제 시작주소가 어딜 가리키는지 감이 왔을 것이라고 생각이 든다. 그럼 100을 들고 있다는 것을 알 수 있다. 배열의 이름을 가지고 + 연산하게 되면 배열의 주소값이 바이트만큼 이동하기 때문에 주소값이 다음 주소값을 가리키게 된다. char는 1 바이트기 때문에 1바이트만큼 증가한 다음 메모리의 주소값을 가리킨다는 것이다. 하지만 0은 아무리 더해봤자 0이다. 따라서 메모리의 주소값은 변동이 없게 되고, 그대로 배열의 시작 주소값을 가리킨다.

 

 이런 식으로 해석해 나간다면 나머지 두 문장도 자연스럽게 해석이 가능하다. *(ptr[0] + 1) 이건 무엇을 의미할까. 우선 연산의 우선순위에 따라서 ()안에 있는 것을 먼저 해석해 보자 ptr[0] + 1이 무엇인지 해석해 본다면 ptr[0] = 10이다. 그럼 거기에다 + 1 더하게 되면 ptr[0]의 메모리 주소값이 10이기 때문에 Korea의 K의 주소값이 10 임을 알 수 있을 것이다. 그렇다면 그 주소값의 + 1이기 때문에 K다음값 O를 가리키게 된다. 그럼 가리키고 있는 상태에서 *(에스터리스크) 역참조 즉 그 메모리의 값을 참조하면? O라는 문자값이 출력되게 된다. 첫 번째는 형식지정자가 %u로 되어있기 때문에 O라는 값의 주소값을 출력하라고 하고 있고, 두 번째는 %c이기 때문에 그 값이 무엇이냐라고 질문해 볼 수 있다.

 

 아마 포인터 배열을 이해할 때 가장 난감한 부분이지 않을까 싶다. 왜냐하면 배열의  시작주소값을 가리킨다는 것이 처음에 그 값이 코드상에서는 "Korea", "itstime" 처럼 문자열 형태로 들어가 있기 때문에 그 문자열의 시작 주소값을 가리킨다고 이해할 수 있기 때문이다. 이게 포인터를 이해하기 어렵게 만든다. 하지만 포인터 배열이 의미하는 것과 위처럼 천천히 찾아나가다 보면 값의 방향이 보다 명확히 잡히는 것을 확인할 수 있다. 간단한 예제일 수 있지만, 포인터 배열의 시작주소값이 의미하는 게 무엇인지, 내가 생각하고 있는 시작주소값이 요소의 시작주소값을 가리킨다고 생각하는 게 아닌지 생각해 볼 필요가 있다.