배열과 포인터

처음 C언어를 배우게 되면 배열과 포인터 부분에서 어려움을 느끼게 된다. 지금부터 그 배열과 포인터에 대해 알자. 배열은 1차원 배열은 기본적으로 알고 있다고 가정하고 2차원배열에 대해 알아보자.

1. 2차원 배열의 포인터를 배열의 행을 참조
2차원 배열은 다음과 같이 선언한다.

자료형 (*포인터명)[열의개수];
Ex) char (*prt)[10];
ptr은 열의 개수가 10개인  2차원배열의 포인터 변수이다.

2차원 배열의 포인터 변수는 선언시 반드시 열의 개수를 명시해주어야 한다. 배열의 행은 명시해주지 않아도 됩니다. 이유는 열의 개수만 알고 있다면, 첫 번째 항목부터 세어나가면 다음 행은 쉽게 알 수 있기 때문이다. 실제 메모리사에는 1차원 배열 형태로 연속적으로 할당된다. 따라서 행은 열의 개수만큼 떨어져 있다.

그렇다면 2차원 배열의 포인터를 증감하면 어떠한 결과가 나오게 될까? 예상한대로 행의 크기만큼 이동하게 된다. ptr + 1처럼 2차원 포인터 변수에 정수값을 더하는 경우, 포인터 변수는 정수값만큼 다음 행으로 이동하여 해당 행의 시작주소를 가리키게 됩니다.

int array[3][4];
int (*ptr)[4] = array;



array       ==   ptr       ==  array[0]  ==  &array[0][0]
array + 1  ==   ptr + 1  ==  array[1]  ==  &array[1][0];
array + 2  ==   ptr + 2  ==  array[2]  ==  &array[2][0];


모든 n차원 배열에서 배열명은 배열의 시작주소인 첫번째 항목의 주소값을 가리킨다. 즉, array == &array[0][0] 과 동일하다. 그런데 프로그램을 실행해보면 array[0] 역시 array[0]과 동일한 주소값을 보관하는 것을 알 수 있다.

그렇다면 array[0] 역시 배열명일까? 답은 맞다. array[0]은 첫 번째행, array[1]은 두번째 행의 시작 주소를 가리킨다. 그러므로 ptr + 1은 array[1]과 동일한 주소값을 가리키는 것이다. 다음은 각 행의 메모리 값이다.

array[0] = 0012FF50
array[1] = 0012FF60
array[2] = 0012FF70


배열의 int형이므로 하나의 값에 4byte씩 할당되므로 각 행마다 결과적으로 16byte씩 이동하게 된다.


2. 과연, 배열 포인터 변수와 배열명은 같은 것일까?
포인터변수가 보관하는 값과 배열명이 가지는 값이 동일하다면 서로 같은것으로 생각할 수 있지 않을까? 배열명 역시 포인터 상수로 볼 수 있으니깐 말이다. 하지만 둘은 서로 참조하는 대상이 같을 뿐이지 실제로는 다르다. 배열명이나 배열의 행첨자는 배열/행의 시작주소값을 가지는 포인터이면서, 그 배열/행 자체이기도 한다.

int array[2][3];
int (*ptr)[3];
ptr = array;

printf("size of array = %d \n", sizeof(array));
printf("size of ptr = %d \n", sizeof(ptr));
printf("size of array[0] = %d \n", sizeof(array[0]));
printf("size of ptr + 1 = %d \n", sizeof(ptr + 1));

Result ======================================================
size of array = 24
size of ptr = 4
size of array[0] = 12
size of ptr + 1 = 4

참조하는 주소가 같았던 array와 ptr의 메모리 크기가 서로 다르게 출력되었다. 이와 같이 배열명은 배열의 첫번째 항목에 대한 주소값을 가지고 있는 포인터인 동시에 그 배열 자체이기도 하다. 이에 반해 포인터는 단순히 변수의 주소값만을 보관하므로, 참조하는 대상이 다르더라도 그 할당된 메모리 크기는 동일하다.


3. 배열의 초기화 및 복사
배열을 초기화 하는 방법에는 무엇이 있을까? 쉽게 생각할 수 있는 것이 중접루프를 통해 해당 값을 초기화 해줄수 있다. 하지만 memset 함수를 사용하면 쉽게 초기화 할 수 있다.

char buf[10];
memset(buf, 'a', sizeof(buf));


위와 같이 memset 함수를 사용하게 하면 buf[0] ~ buf[10]까지 배열의 값을 'a'로 초기화 할 수 있다.

하지만 memset() 는 char 형에서만 가능하다. int형의 경우 메모리크기가 4바이트라고 할 때 4바이트를 모두 사용하여 하나의 정수값을 표현하는데 memset을 사용하면 1바이트마다 지정한 문자값으로 설정하여 원하지 않는 값이 나오게 된다.

하지만 0으로 초기화하는 경우는 1바이트마다 0으로 초기화해도 4바이트는 결국 0이므로 0을 초기화하는 경우에는 사용가능하다.


2차원 배열 포인터로 배열 내 항목 다루기
int array[2][3];
int (*ptr)[3];
ptr = array;

위와 같이 선언한 경우 배열내 항목에 접근하려면 다음과 같이 사용하면 된다.
ptr                     ==     array;
ptr + 1                ==     array + 1;
*ptr                    ==    array[0];
*(ptr + 1)            ==     array[1];
*(*(ptr + 0) + 0)   ==     array[0][0];
*(*(ptr + 0) + 1)   ==     array[0][1];
*(*(ptr + 0) + 2)   ==     array[0][2];
*(*(ptr + 1) + 0)   ==     array[1][0];
*(*(ptr + 1) + 1)   ==     array[1][1];
*(*(ptr + 1) + 2)   ==     array[1][2];




4. 2차원 배열 포인터를 함수에 넘기기
2차원 배열 포인터를 함수에 넘겨 Call-by-Reference 로 사용하려면 어떡해 해야 할까? 답은 간단하다. 포인터 선언과 같은 형식으로 넘겨준다. 다음은 위에서 예로 살펴본 array[2][3]을 함수에 넘겼을 때 함수에서 사용되는 형식이다.
void array_tran(int (*ptr)[3], int a, int b)
{
      ... ...
}

함수에서 배열을 사용할 때도 위와같이 동일하게 배열의 요소에 접근하면 된다. 단, 함수를 호출시에 넘겨줘야 하는 인자값은 넘겨줄 배열의 시작주소를 가지고 있는 배열의 이름이나 포인터 자체여야 한다.

Reference : C/C++ 개발자를 위한 KIN 포인터 실무


[프로그래밍/_ C/C++] - [C언어] 포인터와 const 키워드
[프로그래밍/_ C/C++] - [C언어] void 형 포인터
[프로그래밍/_ C/C++] - [C언어] 널포인터(NULL)
[프로그래밍/_ C/C++] - [C언어] 포인터의 포인터
[프로그래밍/_ C/C++] - [C언어] 포인터 배열
[프로그래밍/_ C/C++] - [C언어] 문자열 포인터 다루기
Posted by Proneer

댓글을 달아 주세요