거북이처럼 천천히

배열 포인터 본문

C

배열 포인터

유로 청년 2024. 6. 19. 16:35

1. 배열 요소의 주소와 배열의 주소

Q) 배열명은 "배열의 첫 번째 원소의 주소 값"을 의미하고 있음을 알고 있다. 그럼 &배열명 은 무엇을 의미하는가?

#include <stdio.h>

int main(void) {
	int list[] = { 1, 2, 3, 4, 5 };

	printf("list = %u\n", list);
	printf("&list = %u\n\n\n", &list);

	printf("list + 1 = %u\n", list + 1);
	printf("&list + 1 = %u\n", &list + 1);

	return 0;
}

list + 1, &list + 1의 연산 결과 값에 주목

 

  • 위 실행 결과를 통해 알 수 있듯이 배열명인 list배열명의 주소인 &list동일한 주소 값을 갖는다.
  • 하지만, 주소 덧셈 연산을 한 결과값은 전혀 다르다.
    ▶ list + 1 = list의 첫 번째 원소 주소값 + 4byte.
    ▶ &list + 1 = list의 첫 번째 원소 주소값 + (4byte * 5) = list의 첫 번째 원소 주소값 + 20byte
  • Q) 왜 동일한 배열에 대해서 서로 다른 결과값이 나오며, 결과값이 의미하는 것이 무엇인가?
  • A) 배열도 사용자가 정의한 자료형이다. 따라서 이를 배열 관점에서 볼 수 도 있지만, 배열을 하나의 자료형으로도 볼수 있다. 따라서 &list + 1가 갖는 의미는 "list 배열 전체를 하나의 자료형으로 보았을 때, &list + 1 == '배열를 한 묶음' 으로 보고, 다음 주소 값을 반환한다.라고 볼 수 있다.
  • list + 1 == list + sizeof(int) == list + 4
  • &list + 1 == list + sizeof(list) == list + 20

 

 

 

 

2. 2차원 배열과 배열 포인터

Q) 1차원 배열명은 첫 번째 원소의 메모리 주소 값을 갖고 있다고 했다. 그럼 2차원 배열명은 어떤 원소, 요소의 수소 값을 갖고 있는가?

A) 2차원 배열명은 첫 번째 배열 요소(배열)의 주소를 가르킨다.

score 배열명은 score[0]의 메모리 공간 주소를 가르킨다.

 

  • 배열 포인터 (Array pointer) : 배열의 시작 주소를 저장하는 포인트 변수
  • 주로 2차원 배열을 포인터에 저장하고 싶을 때, 사용한다.
  • 다음 예시를 통해 살펴보자.
#include <stdio.h>

int main(void) {
	// int 형 2차원 배열 생성
	int board[][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };

	// 2차원 배열 board에 주소 연산자를 사용함으로서 
	// 배열이 가르키는 첫 요소의 주소 값을 얻는 동시에
	// 배열 전체를 하나의 묶음으로 본다.
	int(*p_board)[4] = &board;

	// Print the board.
	printf("Board = \n");
	for (unsigned short row = 0; row < sizeof(board) / sizeof(board[0]); row++) {
		for (unsigned short col = 0; col < sizeof(board[0]) / sizeof(board[0][0]); col++)
			printf("%3d", p_board[row][col]);
		printf("\n");
	}

	return 0;
}

 

코드 해석) 

  • Q) board 배열명이 가르키는 주소값은 무엇인가?
  • board는 첫 번째 요소인 {1, 2, 3, 4}의 시작 주소를 가리키며, 좀 더 구체적으로 설명하면, board는 메모리에서 {1, 2, 3, 4}의 첫 번째 요소인 1이 저장된 메모리 위치를 가리키고 있다.
  • Q) int (*p_board)[4] = &board; 가 갖는 의미는?
  • &board : 배열명이 가르키는 첫 번째 요소의 주소 값을 의미하며, 
                    배열 board를 하나의 묶음으로 보겠다는 것을 의미한다.
  • int (*p_board) [4] : 열의 갯수가 4인 2차원 배열의 주소 값을 포인터에 저장한다.

 

 

 

           

3. 주의) 포인터 배열과 배열 포인터의 차이

이 부분은 상당히 혼란을 야기할 수 있는 부분이기 때문에 정확하게 이해하고 넘어가자.

 

3-1) 정의

  • 포인터 배열 : 배열의 각 원소들의 자료형이 포인터인 배열
  • 배열 포인터 : 배열명이 가르키는 첫 번째 요소의 주소 값을 저장하는 포인터

 

3-2) 선언 (주의)

  • 포인터 배열 : int *p_data [5]
  • 배열 포인터 : int (*p_data)[4]

 

3-3) 활용

  • 포인터 배열 : 2차원형태의 배열 생성하고 싶은 경우
  • 배열 포인터 : 2차원 배열을 포인터 변수에 저장하고 싶은 경우

 

 

 

4. 배열 포인터의 활용 -
    ( 배열이 가르키는 주소 값을 포인터 변수에 저장한 뒤, 인자로 전달 )

#include <stdio.h>

// Function prototype.
void print_the_board(int(*p_board)[4], int max_row, int max_col);

// Main method
int main(void) {
	int board[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {10, 11, 12, 13} };
	
	int row = sizeof(board) / sizeof(board[0]);
	int col = sizeof(board[0]) / sizeof(board[0][0]);

	print_the_board(board, row, col);

	return 0;
}

// Print the board.
void print_the_board(int(*p_board)[4], int max_row, int max_col) {
	printf("Board = \n");

	for (int row = 0; row < max_row; row++) {
		for (int col = 0; col < max_col; col++)
			printf("%5d", p_board[row][col]);

		printf("\n");
	}
}

 

  • caller측에서 callee에게 2차원 배열을 argument 값으로 전달하면 callee측에선 2차원 배열을 넘겨 받기 위해 배열 포인터를 이용하여 받게 된다.
  • 즉, caller 측에서 2차원 배열을 전달하면 calle 측에서는 배열 포인트을 통해 받을 수 있다.

 

 

 

 

 

 

4. Callee 측에서 2차원 배열을 받을 수 있는 방법에는 배열 포인터 말고 없는가? 
    이중 포인터로 받으면 안되는가?

#include <stdio.h>

// Function prototype.
void print_the_board(int** board, int max_row, int max_col);

// Main method
int main(void) {
	int board[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };

	int row = sizeof(board) / sizeof(board[0]);
	int col = sizeof(board[0]) / sizeof(board[0][0]);

	print_the_board(board, row, col);

	return 0;
}

// Print the board
void print_the_board(int** board, int max_row, int max_col) {
	for (int row = 0; row < max_row; row++) {
		for (int col = 0; col < max_col; col++) printf("%5d", board[row][col]);
		printf("\n");
	}
}
  • 작성자는 "꼭 Callee 측에서 2차원 배열을 받기 위해서는 배열 포인터를 사용하는가? 이중 포인터로도 받을 수 있지 않을까?"에 대한 궁금이 생겨서 위와 같이 코드를 작성하였다.
  • 결론은 위와 같이 코드를 작성할 경우, 실행이 불가능하다.
  • Q) callee 측에서 2차원 배열을 받기 위해서 parameter 값을 이중 포인터형태로 선언했는데, 왜 받을 수 없는가?
  •            
    이중 포인터는 포인터의 주소 값을 저장하는 포인터이다. 만약 이중 포인터로 받고 싶다면 Caller 측에서 전달되는 arguement 값은 포인터의 주소 값이여야 할 것이다. 하지만, Caller 측에서 argument 값으로 전달하는 값은 무엇인가? Caller 측에서 argument 값으로 전달되는 값은 첫 번째 원소의 주소 값이다. 
  • Q) 그럼, 2차원 배열을 Callee측에서 이중 포인터로 받기 위해서 어떻게 해야 하는가?
#include <stdio.h>

// Function prototype.
void print_the_board(int** board, int max_row, int max_col);

// Main method
int main(void) {
	int board[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };

	int row = sizeof(board) / sizeof(board[0]);
	int col = sizeof(board[0]) / sizeof(board[0][0]);

	// 포인트 배열 선언
	int* p_board[3];

	for (int row = 0; row < 3; row++)
		p_board[row] = board[row];

	print_the_board(p_board, row, col);

	return 0;
}

// Print the board
void print_the_board(int** board, int max_row, int max_col) {
	for (int row = 0; row < max_row; row++) {
		for (int col = 0; col < max_col; col++) printf("%5d", board[row][col]);
		printf("\n");
	}
}
  • Caller 측에서 2차원 배열을 전달하는 것이 아닌 포인터 배열 형태로 전달하면 Callee 측에서 해당 포인트 배열을 받기 위해 2중 포인터로 받을 수 있다.

 

 

 

정리

Caller 측에서 Callee에게 2차원 배열을 전달하는 방법

 

  • 2차원 배열을 전달하고 싶다면 Callee측에서 배열 포인트 형태로 받으면 된다.
  • 포인트 배열을 전달하고 싶다면 Callee측에서 이중 포인터로 받으면 된다.
  • 이중 포인터를 전달하고 싶다면 Callee측에서 이중 포인터로 받으면 된다.
    (단, Callee 측에서 이중 포인터의 값을 수정하지 않는다는 전제하에)

'C' 카테고리의 다른 글

구조체의 메모리 공간 크기  (0) 2024.06.20
함수 포인터  (0) 2024.06.19
이중 포인터  (1) 2024.06.19
Pointer arry (포인터 배열)  (0) 2024.06.18
Register variable  (0) 2024.06.18