C, C++/C++ 언어 / / 2024. 4. 30.

[C++] 포인터로 배열에 접근하기

배열명과 포인터

배열은 인덱스를 통해 접근되는 데이터를 담은 데이터 구조입니다.

 

예를 들면, 1차원 배열은 다음과 같습니다.

int array[5] = {1,2,3,4,5};
char str[10] ="\0";

 

그리고, 배열에 대한 포인터 선언은 [ 배열 멤버의 데이터 타입* 포인터명 ] 형식을 가집니다.

포인터의 목적이 배열의 멤버에 접근하여 사용하는 것이기 때문입니다.

 

배열   array   멤버의 데이터 타입은 int이고, 이 배열을 가리키기 위한 포인터 선언은 int 변수에 대한 포인터 선언과 같습니다.

int a = 10;
int* pA = &a;	// &: a의 주소를 알려주는 연산자

int array[5] = {1,2,3,4,5};
int* pB = array;	// 배열명

 

하지만 변수와 달리 배열명은 배열의 주소를 가리키고 있습니다.

위의 예문처럼 주소 연산자를 사용하지 않아도 주소를 바로 알 수 있죠.

정확히 말하면 배열명은 배열의 첫 번째 멤버의 주소를 가리킵니다.

그래서, 항상 배열 array에 대하여   array = &array[0]   이 됩니다.

 

그럼, 배열명과 포인터를 통해 데이터에 접근하는 방법을 예제를 통해 보겠습니다.

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

int a1 = array[1];
int a2 = *(array+1);

int* pArray = array;	// int형 포인터

int b1 =  pArray[1];
int b2 = *(pArray+1);

위와 같이, 배열명과 포인터가 데이터에 접근하는 방식이 같다는 것을 알 수 있습니다.

그럼 포인터 대신 배열명을 사용할 수 있지 않을까요?

 

포인터 대신 배열명을 사용하기엔 다른 점들이 있습니다.

 

첫 번째, 배열명은 상수입니다. 

두 번째, 포인터와 배열명은 데이터 타입이 다릅니다.

 

위의 예문의   array   는 int를 멤버로 갖는 배열이지만,   pArray   는 int타입 포인터입니다.

이는 sizeof 연산자를 통해서도 포인터와 배열명의 데이터 타입이 다르다는 것을 쉽게 알 수 있습니다.

그리고, 이전 글에서 강조했듯이 포인터는 변수입니다.

 

[C++] 포인터의 개념 및 사용하는 이유

포인터의 정의포인터란 객체의 메모리 주소를 저장하는 변수입니다.여기서 객체란 변수, 배열, 구조체, 클래스, 함수 등 메모리에 저장되는 모든 객체를 말합니다. 포인터의 정의에서 가장 중

codingembers.tistory.com

 

그렇기 때문에, 포인터를 사용할 땐 가능한 연산들이, 배열명을 사용할 때는 컴파일조차 할 수 없는 경우도 있습니다.

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

array = array+1;	// compile error !!
array++;		// compile error !!
array = &(array[2]);	// compile error !!
    
int* pArray = array;

pArray = pArray + 1;	// ok
pArray++;		// ok
pArray = &(pArray[2]);	// ok

int n0 = sizeof(array[0]);	// n0: 4
int n1 = sizeof(array);		// n1: 20
int n2 = sizeof(pArray);	// n2: 8 (64bits)

 

참고로,

 

위의 예문에서   array   배열의 멤버수는   20 / 4 = 5   입니다.

 

 

이차원 배열

int array1[12];		
int array2[3][4];

int array3[3][5];

위의   array1, array2   두 배열은 메모리 상에 배치된 모습이 똑같습니다. 차지하는 메모리 크기도 같고요.

그렇지만 데이터 타입이 다릅니다. 

둘 다 같은 int형 배열이 아니라는 거죠.

 

  array1   은 int 배열이고, 

array2는 int [4] 배열의 배열입니다.

 

위의   array1   과   array2   는 배열의 데이터에 접근하는 방식도 다릅니다.

  &array1 [1]   로 다음 멤버의 주소를 읽으면   array1   보다 4바이트 더 큽니다.

하지만   &array2 [1]   는   array2   보다 16바이트 더 큽니다.   array2   의 멤버는 int [4] 배열이기 때문이죠.

  +, -   연산자를 사용할 때도 결과가 완전히 다르죠.

 

마찬가지로,   array2   와   array3   도 데이터 타입이 다르게 취급됩니다.

 

또한, int 포인터로   array2   를 가리키지 못합니다.

아래의 코드는 컴파일 오류가 발생합니다.

int array2[3][4];
int* pArray = array2;	// compile error!!

이는 전의 항목에서 설명한 바와 같이, 배열을 가리키기 위해선 배열 멤버의 데이터 타입형 포인터를 사용해야 하기 때문입니다.

  array   의 배열 멤버의 데이터 타입은 "int [4] 배열" 타입입니다.

 

이러한 2차원 배열을 가리키는 포인터의 선언은 다음과 같습니다.

int array[3][4];
int (*p)[4] = array2;

"int [4] 배열" 타입을 가리키는 포인터에 대한 선언 방식입니다.

 

만약, 위의 선언에서 괄호를 없애면 어떨까요? 입력 시 불편하기도 하고 말이죠.

int *p[4];

이것은 int 포인터를 멤버로 갖는 배열입니다. 멤버의 수가   4   개인 포인터들의 1차원 배열이죠.

 

그리고 배열을 지정하는 포인터를 선언하기 위해서 [ 배열 멤버의 데이터 타입* 포인터명 ]을 사용했습니다.

위의 배열   p   를 지정하기 위한 포인터 선언은 다음과 같습니다.

int *p[4];
int **pp = p;

 

따라서, 2차원 배열을 가리키는 포인터 선언에서 괄호를 빼선 안되고,  위의 포인터 배열의 포인터는 2차원 배열의 포인터와 가리키는 데이터 타입이 전혀 다르다는 것을 이해해야 합니다.

 

그럼 3차원 배열을 가리키는 포인터는 어떻게 선언해야 할까요?

int array[3][4][5];
int (*pArray)[4][5] = array;

  pArray   는 int [4][5] 배열을 멤버로 갖는 배열을 가리키는 포인터라고 할 수 있습니다.

 

 

정리

  • 배열명은 배열의 첫 번째 멤버의 주소를 가리킨다.
  • int [3][4] 배열은 int [4] 배열의 배열이다.
  • 위의 배열을 가리키기 위한 포인터는 int (*ptr)[4] 형식을 갖는다.

 

 

이 글과 관련 있는 글들

C-style 배열과 배열의 붕괴( array decay )에 대하여

 

 

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유