티스토리 뷰

카테고리 없음

배열과 포인터란 무엇인가 심화편.

이화에월백하고 2022. 4. 17. 13:38

2023.03.02. 내용 수정

안녕하세요? 이화입니다. 저번에 못 쓴 내용을 마저 써 보겠습니다. 제목이 좀 바뀌었죠? 내용이 바뀌니까 자연스레 제목이 부자연스러워져서 조금 바뀌었지만, 변수와 포인터란 무엇인가 기초편에서 이어지는 내용입니다.


이 글은 다음에 해당하는 분들을 위한 글입니다.

그리고 이 글은 다음 내용을 담고 있습니다.

  • 배열이란 무엇인가
  • 배열과 포인터에는 무슨 관계가 있는가
  • 배열을 함수의 매개변수로 넘겨주는 방법
  • 포인터에 수를 더하고 빼는 건 무슨 의미인가


자. 시작해 볼게요.

1. 배열이란 무엇인가

이번엔 배열이 무엇인지부터 시작합니다. 여러분은 연속된 값을 표현하기 위해 여럿 선언한 적이 있을 겁니다. 아래 코드처럼요.

int a, b, c, d, e;

그런데, 이러면 문제가 있습니다. 변수를 참조할 떄 반복문을 쓸 수가 없다는 겁니다. a부터 e까지 전부 1씩 더하려면 어떻게 해야 할까요? 하나하나 하는 것 외에는 딱히 방법이 생각나지 않습니다. 이런 상황을 위해 배열이 존재합니다.

int arr[5];

arr은 array의 약자입니다. 이렇게 선언된 배열은 다음과 같이 사용할 수 있습니다.

arr[0] = 1; 
arr[1] = 2;	//이렇게 상수로 index를 지정해 변수처럼 쓰실 수 있습니다.

arr[i] = 3;	//아니면, 상수 대신 변수를 통해 참조할 수도 있습니다.

그런데, 주의할 점이 있습니다. 5칸짜리 배열은, 0~4의 인덱스를 가진다는 점입니다. 이 점에 대해서는, 이 글을 전부 읽고 나면 왜 그런지 알 수 있습니다. 이렇게 배열에 대해서 간단히 알아보았습니다. 하지만, 배열과 포인터 간에는 어떤 관계가 있는 걸까요?

2. 배열과 포인터의 관계

거두절미하고 말하자면, 배열은 포인터입니다.[각주:1] 이게 뭔 소리냐고요? 말 그대로입니다. 배열은 포인터 변수의 일종입니다. 우리가 배열을 선언하면, 컴퓨터는 메모리에서 연속된 공간을 할당합니다. 한번 int형 배열 arr을 선언하고, &arr[0]값과, &arr[1]값을 비교해 보세요. 적당한 크기의 정수형(int형은 모자라고, long long형 정도면 됩니다) 또는 %p를 사용해 출력한다면, int형이 4바이트인 대부분의 환경에서는 4만큼 차이가 나는 연속된 숫자일 겁니다. 이렇게 할당된 공간의 맨 처음 값을 가리키는 포인터가 바로 배열의 정체입니다. 한번 볼까요?

int arr[5];
int *ptr;
// 여기서 뭔가 arr의 내용을 채웠다고 가정하죠!

ptr = arr; //그냥 대입해요!

ptr[1] -= 10; //그럼 여기서는 arr[1]이 수정됩니다!

그럼 배열의 요소를 참조할 때 뒤에 붙는 []의 정체는 뭘까요? 이건 인덱스 연산자라고 부릅니다. 포인터 변수 뒤에 붙여서, 거기서 얼마만큼 이동한 위치의 값을 출력할지에요. 그럼 이제 왜 5칸짜리 배열이 0에서 4까지의 인덱스를 가지는지 알 수 있습니다. 처음 출발한 위치에서 바로 앞에 있는 변수를 읽으려면, 0만큼 움직여야 하기 때문이죠!!
혹시 눈치챈 분 있나요? arr[0]은 *arr과 동일한 작동을 합니다!

3. 배열을 함수의 매개변수로 넘겨주는 방법.

이건... 위를 이해하셨다면 사실 설명할 것도 없습니다.

int f1(int *arr);
int f2(int arr[]);

두 방법 모두 같습니다! 하지만 전달 과정에서 포인터를 사용하는 건 다르지 않습니다. 배열은 포인터의 일종이기 때문이죠. 이때 한 가지 주의할 게 있습니다. 배열을 매개변수로 받는다면, 항상 같은 길이의 배열을 받는 게 아닌 이상 배열의 길이를 다른 매개변수로 전달해 주는 게 좋습니다. C는 배열의 길이를 확인할 방법이 없기 때문에, 얼마나 배열을 읽어야 할지 알 수 없기 때문이죠. [각주:2]

4.포인터에 수를 더하고 빼는 건 무슨 의미일까.

아까 전에 배열의 주소값을 출력했을 때, 4만큼 차이가 나는 걸 보셨을 겁니다. 그러면 우리가 4씩 직접 더해도 될까요?
정답은 "아니다" 입니다. 4가 아니라 1을 더해야 합니다. 포인터 변수에 수를 더하고 뺄 때, 포인터 변수에 저장된 주소 값은 자료형의 크기(sizeof 연산자를 통해 확인할 수 있습니다.)만큼 변동됩니다. 배열로 치면 다음 요소로 넘어간다, 라고 생각할 수 있습니다. 앗! 그럼 이제 우린 인덱스 연산자 ( [] )의 동작을 정확히 알 수 있습니다. 아래 두 코드는 완전히 같은 코드입니다.[각주:3]

arr[i];

*(arr + i)

이걸 잘 응용하면, 반복문, 특히 while문을 통해 배열을 효과적으로 순회할 수 있습니다. 하지만 여기서 설명하진 않겠습니다. [각주:4]첫째 이유는 인덱스를 사용하는 방법만으로도 충분하다는 것입니다. 그리고 두 번째 이유는 가독성입니다. 포인터는 다루기 힘든 개념이지만, 제일 힘든 건 포인터를 사용한 코드를 읽는 겁니다. 포인터의 증감 연산을 적절히 사용하지 않으면 코드를 읽기가 매우 어려워집니다. 충분히 이해가 되지 않았다면 쓰지 마시고, 충분히 이해가 되었다면 자연스럽게 쓰실 수 있을 겁니다.

이번엔 글이 특히 짧았네요. 둘로 나뉘어서 그렇습니다. 그래도 어제 쓰려던 글에 마저 이어쓰기는 조금 길다고 생각했습니다. 수정하면 다시 읽기 불편하기도 하고요. 미진한 글이지만 도움이 되셨다면 좋겠습니다. 궁금하신 내용은 트위터 계정 으로 연락주시면 재빠른 대답 드리겠습니다. 읽어주셔서 감사합니다.


  1. 엄밀히 말해서, 배열은 포인터이지만 그 역은 성립되지 않으므로 "배열은 포인터의 일종입니다"라고 할 수 있습니다. ptr이 포인터이고 arr이 배열일 때, ptr = arr;는 되지만 arr = ptr;은 성립하지 않기 때문입니다.
    [본문으로]
  2. 문자열을 전달할 때와 같은 경우에는, 특정 값을 이용해 끝을 표시하는 방법으로 배열을 얼마나 읽어야 할지 확인하기도 합니다. 앞에서부터 값을 읽다가, 정해진 값을 만나면 읽는 것을 그만두는 것이죠. [본문으로]
  3. 이와 같이 동일한 역할을 하는 코드를 조금 더 이해하기 쉬운 코드로 표현할 수 있게 하는 문법을 Syntactic sugar(문법 설탕)라고 합니다. [본문으로]
  4. 코드만 한번 보여드리자면, 다음과 같습니다.

    #include <iostream>
    using namespace std;
    
    int main() {
        int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        
        for(int *i = a; i < (a + 10); i++) {
            cout << *i << endl;
        }
        
        return 0;
    }

    위 코드를 실행시키기 전에 결과를 예상해 보고, 실행해서 어떤 일이 생겨나는지 정확히 설명할 수 있다면, 당신은 포인터 마스터입니다!! [본문으로]

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함