11-3-나.배열 인수

배열 포인터는 자신이 가리키는 대상체 배열의 크기를 정확하게 알고 있다. 그래서 타입이 다른 배열은 이 변수에 대입할 수 없다. 다음 예를 보자.

 

int ari[][7]={

     {1,2,3,4,5,6,7},

     {8,9,10,11,12,13,14}

};

 

int (*pa)[7];

int (*pb)[8];

 

pa=ari;

pb=ari;              // 여기서 에러 발생

 

ari 배열은 2차 첨자의 크기가 7인 정수형 2차 배열이다. pa는 이 배열의 부분 배열을 가리킬 수 있는 배열 포인터로 선언되었으므로 pa에 ari의 부분 배열의 번지를 대입할 수 있다. 반면 pb는 2차 첨자가 8인 배열을 가리키는 배열 포인터이므로 pb에 ari의 부분 배열의 번지를 대입할 수 없다. 컴파일러는 이 단계에서 int (*)[7] 타입을 int (*)[8] 타입으로 바꿀 수 없다는 에러 메시지를 출력할 것이다. 물론 pb=(int (*)[8])ari; 로 강제 캐스팅할 수는 있다.

이처럼 배열 포인터는 자신이 가리킬 수 있는 배열의 타입과 크기를 정확하게 기억하고 있기 때문에 사용자의 부주의한 대입을 막을 수 있다. 위 예처럼 일부러 틀린 타입을 대입할 리는 없겠지만 함수로 배열을 넘길 때는 의도하지 않은 실수를 할 수도 있을 것이다. 배열 포인터를 사용하면 정확한 타입만 대입할 수 있으므로 이런 실수를 방지할 수 있다. 다음 예제는 이런 2차원 배열 포인터를 사용하는 예를 보여준다.

 

: ArrayArg

#include <Turboc.h>

 

int GetTotalForWeek(int (*pa)[7])

{

     int i,sum=0;

 

     for (i=0;i<7;i++) {

          sum += pa[0][i];

     }

     return sum;

}

 

void main(void)

{

     int ari[][7]={

          {1,2,3,4,5,6,7},

          {8,9,10,11,12,13,14},

          {15,16,17,18,19,20,21}

     };

 

     int i;

     for (i=0;i<3;i++) {

          printf("%d주의 판매량 = %d\n",i+1,GetTotalForWeek(&ari[i]));

     }

}

 

GetTotalForWeek 함수는 크기 7의 정수형 배열을 인수로 받아들여 이 배열의 총합을 구해 리턴하는데 가령 일주일간의 총 판매량을 구한다고 생각하면 될 것이다. 일주일이 7일이라는 것은 누구나 다 아는 사실이고 불변의 법칙이므로 이 함수는 크기 7의 정수형 배열만 인수로 받아들이도록 되어 있다.

이 함수는 인수로 전달된 pa가 크기 7의 정수형 배열이라는 것을 이미 알고 있으므로 루프는 7까지만 돈다. 크기가 7이 아닌 배열은 이 함수로 전달될 수가 없으므로 7까지 무조건 돌아도 안전하다는 것을 보장받을 수 있다. 함수 본체에서 pa로 전달된 배열의 n번째 요소를 읽을 때는 pa[0][n]으로 읽어야 하는데 pa가 2차원 배열 포인터이기 때문이다. 또는 (*pa)[n]으로도 읽을 수 있는데 *pa 자체가 1차원 배열이기 때문이다.

호출원에서는 각 주별 총 판매량을 구하고 싶을 때 크기 7의 배열 포인터를 이 함수로 전달하면 된다. 예제에서는 ari 2차원 정수형 배열을 정의하고 이 배열의 부분 배열들의 번지를 차례대로 이 함수로 전달했다. 실행 결과는 다음과 같다.

 

1주의 판매량 = 28

2주의 판매량 = 77

3주의 판매량 = 126

 

GetTotalForWeek 함수의 원형에 배열 포인터를 사용함으로써 크기 7의 정수형 배열 이외에는 어떠한 인수도 전달받지 않는다. 이 함수에 크기 8의 정수형 배열이나 크기 7의 실수형 배열에 대한 포인터를 전달하려고 하면 컴파일러가 이를 문법적인 에러로 보고하므로 뜻하지 않은 실수를 방지할 수 있을 것이다.

실인수가 형식인수로 전달되는 것은 일종의 대입 연산이기 때문에 반드시 타입이 일치하거나 아니면 int와 short 처럼 컴파일러가 알아서 타입을 변환할 수 있는 호환되는 타입이어야 한다. 만약 대입 연산식의 좌우변이 일치하지 않으면 캐스트 연산자로 강제로 타입을 바꿀 수는 있다. 배열 포인터의 경우도 캐스트 연산자를 사용할 수 있는데 다음이 그 예이다.

 

int ar[]={1,2,3,4,5,6,7};

int ar2[]={1,2,3,4,5,6,7,8,9,10};

int (*pa)[7];

 

pa=&ar;

pa=&ar2;

 

pa 배열 포인터는 2차 첨자가 7인 배열의 번지를 가지도록 선언되었다. 이 변수에 크기 7의 1차 배열 ar의 번지를 대입할 수 있는데 왜냐하면 ar은 비록 1차원 배열이지만 첫 번째 첨자가 1인 2차원 배열(ar[1][7])로 볼 수 있기 때문이다. 위 코드의 ar 배열은 앞 예제의 ari배열의 부분 배열 ari[0]와 완전히 같은 자격을 가지며 따라서 pa에 대입할 수 있다.

ar2 배열은 크기가 10이기 때문에 pa에 곧바로 대입할 수 없으면 컴파일러는 int (*)[10]을 int (*)[7]로 바꿀 수 없다는 에러 메시지를 출력할 것이다. 만약 ar2 배열의 크기를 잠시 7로 바꾸어 pa에 대입하고 싶다면 다음과 같은 캐스트 연산자를 사용하면 된다.

 

pa=(int (*)[7])&ar2;

 

이 캐스트 연산식에 사용된 int (*)[7]이라는 타입이 바로 크기 7의 배열 포인터라는 뜻이다. 좀 어색해 보이는데 이렇게 생각해 보자. int i;의 타입은 int고 int *pi;의 타입은 int *인데 변수 선언문에서 변수를 빼 버리면 바로 타입이 된다. 그러므로 int (*pa)[7]; 선언문에서 변수명 pa을 빼 버리면 int (*)[7]만 남고 이것이 타입이 되는 것이다. 다음에 배울 함수 포인터의 캐스트 연산자도 이런 식으로 만든다.