포인터 그리고 배열
반응형

배열이 선언되면 컴파일러는 배열의 크기만큼 연속된 메모리 공간을 할당합니다. 그리고 배열의 첫 번째 요소의 주소를 기본 주소로 할당합니다.

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

만약 위와 같이 크기가 5인 int 배열을 선언하고 컴파일러가 기본 주소를 1000으로 할당했다면 아래와 같은 연속된 공간이 할당됩니다.

여기서 배열의 요소가 증가함에 따라 기본 주소에서 4바이트씩 증가함을 볼 수 있습니다. 그 이유는 int의 크기가 4바이트이기 때문입니다. 또한 배열의 변수명(arr)는 배열의 첫 번째 요소를 가리키는 포인터입니다. 즉 배열명은 기본 주소를 가리키고 있습니다.

정리하면 배열명은 배열의 이름이면서 배열의 첫 번째 주소를 가리키는 포인터입니다. 결과적으로 포인터로 배열 arr를 가리키도록 선언할 수 있습니다.

int *p;
p = arr;
// 또는, 
p = &arr[0]; // 배열의 첫 번째 요소(arr[0])의 주소를 가리킴

이렇게 포인터로 배열을 가리킴으로써 증감 연산자(++)로 배열의 다음 요소에 접근할 수 있게 됩니다.

p++; 혹은 p + 1

하지만 감소 연산자(–)로 증가된 포인터를 감소 시킬 수는 없습니다.

배열 포인터

#include <stdio.h>

int main()
{
    int i;
    int a[5] = {1, 2, 3, 4, 5};
    int *p = a;     // 포인터 변수 p에 배열 a의 기본 주소를 저장
    for (i = 0; i < 5; i++)
    {
        printf("%d", *p);
        p++; // 증감 연산자로 배열의 다음 요소로 이동
    }
    
    return 0;
}

포인터 *p를 사용하여 배열에 저장된 값을 하나씩 출력하고 증감 연산자(p++)를 사용하여 배열의 다음 요소로 이동합니다.

위 예제에서 printf(“%d”, *p); 부분을 아래와 같이 변경하여 출력할 수 있습니다.

printf(“%d”, a[i]); 

배열 요소의 인덱스를 증가하여 값을 출력

printf(“%d”, i[a]); 

위와 같이 각 배열의 요소를 출력

printf(“%d”, a + i); 

각 배열 요소의 주소를 출력

printf(“%d”, *(a + i)); 

각 배열 요소의 주소에 포함된 값을 출력

printf(“%d”, *a); 

첫 번째 요소의 값을 출력

a++; 

컴파일 에러 – 배열의 기본 주소는 변경 불가능

배열의 값에 접근하기 위한 일반적인 형태는 아래와 같습니다.

*(a+i)
혹은
a[i]

2차원 배열 포인터

2차원 배열도 마찬가지로 배열명은 기본 주소 즉 배열의 제일 첫 요소의 주소를 가리킵니다. 만약 int arr[3][3] 선언한 경우 아래와 같은 테이블 형태로 추상화할 수 있습니다.

이것은 순전히 우리가 이해하기 쉽게 표현한 것이고 배열은 연속된 메모리 공간을 할당받으므로 메모리상에는 아래 그림과 같을 것입니다.


그럼 위와 같이 2차원 배열의 값에 접근하기 위해선 포인터를 어떻게 사용해야 할까요? 위의 그림을 좀 더 자세히 표현하면 아래와 같습니다.

arr는 2차원 배열 전체의 첫 주소를 가리키고 있고 각 arr[0], arr[1], arr[2]는 각 행의 첫 주소를 가리키고 있습니다. 다시말해 arr[3][3]는 세 개의 행과 세 개의 열로 이루어져 있다고 생각하시면 됩니다. 값에 접근하기 위해서는 행과 열의 조합을 사용하여 다음과 같이 arr[i][j] 사용하면 됩니다.

이것을 포인터로 표현하면 arr[i][j]에 접근하기 위해서는 *(*(arr + i) + j)를 사용합니다. 풀어서 설명하면 아래와 같습니다.

*(arr + i): i번째 행의 첫 번째 주소로 이동. 해당 행의 기본 주소
*(*(arr + i) + j): i번째 행의 기본 주소에서 j번째의 값에 접근

포인터 그리고 문자열

포인터를 사용하여 문자열을 할당할 수도 있습니다.

char *str = "Hello";

위의 코드는 Hello라는 문자열을 작성한 후 주소를 str 포인터 변수에 저장한 형태입니다. str는 문자열의 첫 문자를 가리키고 있습니다.

여기서 주목해야 할 점은 char 포인터를 사용하면 실행(runtime) 시에 문자열을 작성하여 값을 저장할 수 있다는 것입니다.

char *str;         // char 포인터 변수 선언
str = "hello";     // 문자열 작성
printf("%s", str); // 문자열 출력

여기서 문자열 출력시 역참조 연산자인 *를 사용하지 않은 이유는 str는 문자열에 대한 포인터이면서 문자열 자체를 나타내는 이름이기 때문입니다.

포인터 배열

포인터 배열은 각 요소에 char 포인터 변수가 들어가 있다고 생각하시면 됩니다. 아래의 예제와 같이 각 요소는 문자열을 가리키고 있습니다. 만약 포인터 배열을 사용하지 않는다면 2차원 배열로 char name[3][20]과 같이 문자열의 길이만큼 크기를 정의해줘야 합니다.

char *name[3] = { 
    "Adam",
    "Chris",
    "Deniel"
};

// 문자열을 저장하기 위해 2차원 배열을 사용한 경우 
char name[3][20] = { 
    "Adam",
    "chris",
    "Deniel"
};

2차원 배열에 문자열을 저장하게 되면 사용하지 않는 메모리 공간까지 정의해야 하는 단점이 있습니다. 이렇듯 메모리 공간의 낭비가 생기므로 포인터 배열을 사용하는 것이 좋습니다.

여기서 메모리 공간의 낭비라고 하는 이유는 문자열을 저장하기 위해 배열을 사용하기 때문입니다. 배열은 연속적인 메모리 공간을 사용하므로 문자열의 최대 길이 만큼 무조건 정의해야 되기 때문입니다.

<< 다음 혼자 공부하는 C언어 이전 >>

 

반응형

'프로그래밍 > C' 카테고리의 다른 글

C언어 이중 포인터  (0) 2019.12.20
포인터 변수 사용 방법  (0) 2019.12.20
C언어 포인터  (0) 2019.12.20
C언어 공용체 (Union)  (0) 2019.12.20
typedef 별칭(Alias) 주기  (0) 2019.12.19